Simpletest Coverage - includes/install.inc

1 <?php
2 // $Id: install.inc,v 1.104 2009/08/12 23:51:19 webchick Exp $
3
4 /**
5 * Indicates that a module has not been installed yet.
6 */
7 define('SCHEMA_UNINSTALLED', -1);
8
9 /**
10 * Indicates that a module has been installed.
11 */
12 define('SCHEMA_INSTALLED', 0);
13
14 /**
15 * Requirement severity -- Informational message only.
16 */
17 define('REQUIREMENT_INFO', -1);
18
19 /**
20 * Requirement severity -- Requirement successfully met.
21 */
22 define('REQUIREMENT_OK', 0);
23
24 /**
25 * Requirement severity -- Warning condition; proceed but flag warning.
26 */
27 define('REQUIREMENT_WARNING', 1);
28
29 /**
30 * Requirement severity -- Error condition; abort installation.
31 */
32 define('REQUIREMENT_ERROR', 2);
33
34 /**
35 * File permission check -- File exists.
36 */
37 define('FILE_EXIST', 1);
38
39 /**
40 * File permission check -- File is readable.
41 */
42 define('FILE_READABLE', 2);
43
44 /**
45 * File permission check -- File is writable.
46 */
47 define('FILE_WRITABLE', 4);
48
49 /**
50 * File permission check -- File is executable.
51 */
52 define('FILE_EXECUTABLE', 8);
53
54 /**
55 * File permission check -- File does not exist.
56 */
57 define('FILE_NOT_EXIST', 16);
58
59 /**
60 * File permission check -- File is not readable.
61 */
62 define('FILE_NOT_READABLE', 32);
63
64 /**
65 * File permission check -- File is not writable.
66 */
67 define('FILE_NOT_WRITABLE', 64);
68
69 /**
70 * File permission check -- File is not executable.
71 */
72 define('FILE_NOT_EXECUTABLE', 128);
73
74 /**
75 * Initialize the update system by loading all installed module's .install files.
76 */
77 function drupal_load_updates() {
78 foreach (drupal_get_installed_schema_version(NULL, FALSE, TRUE) as $module => $schema_version) {
79 if ($schema_version > -1) {
80 module_load_install($module);
81 }
82 }
83 }
84
85 /**
86 * Returns an array of available schema versions for a module.
87 *
88 * @param $module
89 * A module name.
90 * @return
91 * If the module has updates, an array of available updates sorted by version.
92 * Otherwise, FALSE.
93 */
94 function drupal_get_schema_versions($module) {
95 $updates = array();
96 $functions = get_defined_functions();
97 foreach ($functions['user'] as $function) {
98 if (strpos($function, $module . '_update_') === 0) {
99 $version = substr($function, strlen($module . '_update_'));
100 if (is_numeric($version)) {
101 $updates[] = $version;
102 }
103 }
104 }
105 if (count($updates) == 0) {
106 return FALSE;
107 }
108
109 // Make sure updates are run in numeric order, not in definition order.
110 sort($updates, SORT_NUMERIC);
111
112 return $updates;
113 }
114
115 /**
116 * Returns the currently installed schema version for a module.
117 *
118 * @param $module
119 * A module name.
120 * @param $reset
121 * Set to TRUE after modifying the system table.
122 * @param $array
123 * Set to TRUE if you want to get information about all modules in the
124 * system.
125 * @return
126 * The currently installed schema version.
127 */
128 function drupal_get_installed_schema_version($module, $reset = FALSE, $array = FALSE) {
129 static $versions = array();
130
131 if ($reset) {
132 $versions = array();
133 }
134
135 if (!$versions) {
136 $versions = array();
137 $result = db_query("SELECT name, schema_version FROM {system} WHERE type = :type", array(':type' => 'module'));
138 foreach ($result as $row) {
139 $versions[$row->name] = $row->schema_version;
140 }
141 }
142
143 return $array ? $versions : $versions[$module];
144 }
145
146 /**
147 * Update the installed version information for a module.
148 *
149 * @param $module
150 * A module name.
151 * @param $version
152 * The new schema version.
153 */
154 function drupal_set_installed_schema_version($module, $version) {
155 db_update('system')
156 ->fields(array('schema_version' => $version))
157 ->condition('name', $module)
158 ->execute();
159 }
160
161 /**
162 * Loads the install profile definition, extracting its defined name.
163 *
164 * @return
165 * The name defined in the profile's _profile_details() hook.
166 */
167 function drupal_install_profile_name() {
168 global $install_state;
169 $profile = $install_state['parameters']['profile'];
170 static $name = NULL;
171
172 if (!isset($name)) {
173 // Load profile details.
174 $function = $profile . '_profile_details';
175 if (function_exists($function)) {
176 $details = $function();
177 }
178 $name = isset($details['name']) ? $details['name'] : 'Drupal';
179 }
180
181 return $name;
182 }
183
184 /**
185 * Auto detect the base_url with PHP predefined variables.
186 *
187 * @param $file
188 * The name of the file calling this function so we can strip it out of
189 * the URI when generating the base_url.
190 * @return
191 * The auto-detected $base_url that should be configured in settings.php
192 */
193 function drupal_detect_baseurl($file = 'install.php') {
194 $proto = $_SERVER['HTTPS'] ? 'https://' : 'http://';
195 $host = $_SERVER['SERVER_NAME'];
196 $port = ($_SERVER['SERVER_PORT'] == 80 ? '' : ':' . $_SERVER['SERVER_PORT']);
197 $uri = preg_replace("/\?.*/", '', $_SERVER['REQUEST_URI']);
198 $dir = str_replace("/$file", '', $uri);
199
200 return "$proto$host$port$dir";
201 }
202
203 /**
204 * Detect all supported databases that are compiled into PHP.
205 *
206 * @return
207 * An array of database types compiled into PHP.
208 */
209 function drupal_detect_database_types() {
210 $databases = array();
211
212 // We define a driver as a directory in /includes/database that in turn
213 // contains a database.inc file. That allows us to drop in additional drivers
214 // without modifying the installer.
215 // Because we have no registry yet, we need to also include the install.inc
216 // file for the driver explicitly.
217 require_once DRUPAL_ROOT . '/includes/database/database.inc';
218 foreach (file_scan_directory(DRUPAL_ROOT . '/includes/database', '/^[a-z]*$/i', array('recurse' => FALSE)) as $file) {
219 include_once "{$file->filepath}/install.inc";
220 include_once "{$file->filepath}/database.inc";
221 $drivers[$file->filename] = $file->filepath;
222 }
223
224 foreach ($drivers as $driver => $file) {
225 $class = 'DatabaseTasks_' . $driver;
226 $installer = new $class();
227 if ($installer->installable()) {
228 $databases[$driver] = $installer->name();
229 }
230 }
231
232 // Usability: unconditionally put the MySQL driver on top.
233 if (isset($databases['mysql'])) {
234 $mysql_database = $databases['mysql'];
235 unset($databases['mysql']);
236 $databases = array('mysql' => $mysql_database) + $databases;
237 }
238
239 return $databases;
240 }
241
242 /**
243 * Database installer structure.
244 *
245 * Defines basic Drupal requirements for databases.
246 */
247 abstract class DatabaseTasks {
248
249 /**
250 * Structure that describes each task to run.
251 *
252 * @var array
253 *
254 * Each value of the tasks array is an associative array defining the function
255 * to call (optional) and any arguments to be passed to the function.
256 */
257 protected $tasks = array(
258 array(
259 'arguments' => array(
260 'CREATE TABLE drupal_install_test (id int NULL)',
261 'Drupal can use CREATE TABLE database commands.',
262 'Failed to <strong>CREATE</strong> a test table on your %name database server with the command %query. %name reports the following message: %error.<p>Are you sure the configured username has the necessary %name permissions to create tables in the database?</p>',
263 TRUE,
264 ),
265 ),
266 array(
267 'arguments' => array(
268 'INSERT INTO drupal_install_test (id) VALUES (1)',
269 'Drupal can use INSERT database commands.',
270 'Failed to <strong>INSERT</strong> a value into a test table on your %name database server. We tried inserting a value with the command %query and %name reported the following error: %error.',
271 ),
272 ),
273 array(
274 'arguments' => array(
275 'UPDATE drupal_install_test SET id = 2',
276 'Drupal can use UPDATE database commands.',
277 'Failed to <strong>UPDATE</strong> a value in a test table on your %name database server. We tried updating a value with the command %query and %name reported the following error: %error.',
278 ),
279 ),
280 array(
281 'arguments' => array(
282 'DELETE FROM drupal_install_test',
283 'Drupal can use DELETE database commands.',
284 'Failed to <strong>DELETE</strong> a value from a test table on your %name database server. We tried deleting a value with the command %query and %name reported the following error: %error.',
285 ),
286 ),
287 array(
288 'arguments' => array(
289 'DROP TABLE drupal_install_test',
290 'Drupal can use DROP TABLE database commands.',
291 'Failed to <strong>DROP</strong> a test table from your %name database server. We tried dropping a table with the command %query and %name reported the following error %error.',
292 ),
293 ),
294 );
295 /**
296 * Results from tasks.
297 *
298 * @var array
299 */
300 protected $results = array();
301
302 /**
303 * Ensure the PDO driver is supported by the version of PHP in use.
304 */
305 protected function hasPdoDriver() {
306 return in_array($this->pdoDriver, PDO::getAvailableDrivers());
307 }
308
309 /**
310 * Assert test as failed.
311 */
312 protected function fail($message) {
313 $this->results[$message] = FALSE;
314 }
315
316 /**
317 * Assert test as a pass.
318 */
319 protected function pass($message) {
320 $this->results[$message] = TRUE;
321 }
322
323 /**
324 * Check whether Drupal is installable on the database.
325 */
326 public function installable() {
327 return $this->hasPdoDriver() && empty($this->error);
328 }
329
330 abstract public function name();
331
332 /**
333 * Run database tasks and tests to see if Drupal can run on the database.
334 */
335 public function runTasks() {
336 // We need to establish a connection before we can run tests.
337 if ($this->connect()) {
338 foreach ($this->tasks as $task) {
339 if (!isset($task['function'])) {
340 $task['function'] = 'runTestQuery';
341 }
342 if (method_exists($this, $task['function'])) {
343 // Returning false is fatal. No other tasks can run.
344 if (FALSE === call_user_func_array(array($this, $task['function']), $task['arguments'])) {
345 break;
346 }
347 }
348 else {
349 throw new DatabaseTaskException(st("Failed to run all tasks against the database server. The task %task wasn't found.", array('%task' => $task['function'])));
350 }
351 }
352 }
353 // Check for failed results and compile message
354 $message = '';
355 foreach ($this->results as $result => $success) {
356 if (!$success) {
357 $message .= '<p class="error">' . $result . '</p>';
358 }
359 }
360 if (!empty($message)) {
361 $message = '<p>In order for Drupal to work, and to continue with the installation process, you must resolve all issues reported below. For more help with configuring your database server, see the <a href="http://drupal.org/node/258">Installation and upgrading handbook</a>. If you are unsure what any of this means you should probably contact your hosting provider.</p>' . $message;
362 throw new DatabaseTaskException($message);
363 }
364 }
365
366 /**
367 * Check if we can connect to the database.
368 */
369 protected function connect() {
370 try {
371 // This doesn't actually test the connection.
372 db_set_active();
373 // Now actually do a check.
374 Database::getConnection();
375 $this->pass('Drupal can CONNECT to the database ok.');
376 }
377 catch (Exception $e) {
378 $this->fail(st('Failed to connect to your %name database server. %name reports the following message: %error.<ul><li>Are you sure you have the correct username and password?</li><li>Are you sure that you have typed the correct database hostname?</li><li>Are you sure that the database server is running?</li></ul>For more help, see the <a href="http://drupal.org/node/258">Installation and upgrading handbook</a>. If you are unsure what these terms mean you should probably contact your hosting provider.', array('%error' => $e->getMessage(), '%name' => $this->name())));
379 return FALSE;
380 }
381 return TRUE;
382 }
383
384 /**
385 * Run SQL tests to ensure the database can execute commands with the current user.
386 */
387 protected function runTestQuery($query, $pass, $fail, $fatal = FALSE) {
388 try {
389 db_query($query);
390 $this->pass(st($pass));
391 }
392 catch (Exception $e) {
393 $this->fail(st($fail, array('%query' => $query, '%error' => $e->getMessage(), '%name' => $this->name())));
394 return !$fatal;
395 }
396 }
397 }
398 /**
399 * @class Exception class used to throw error if the DatabaseInstaller fails.
400 */
401 class DatabaseTaskException extends Exception {
402 }
403
404 /**
405 * Replace values in settings.php with values in the submitted array.
406 *
407 * @param $settings
408 * An array of settings that need to be updated.
409 */
410 function drupal_rewrite_settings($settings = array(), $prefix = '') {
411 $default_settings = 'sites/default/default.settings.php';
412 drupal_static_reset('conf_path');
413 $settings_file = conf_path(FALSE) . '/' . $prefix . 'settings.php';
414
415 // Build list of setting names and insert the values into the global namespace.
416 $keys = array();
417 foreach ($settings as $setting => $data) {
418 $GLOBALS[$setting] = $data['value'];
419 $keys[] = $setting;
420 }
421
422 $buffer = NULL;
423 $first = TRUE;
424 if ($fp = fopen(DRUPAL_ROOT . '/' . $default_settings, 'r')) {
425 // Step line by line through settings.php.
426 while (!feof($fp)) {
427 $line = fgets($fp);
428 if ($first && substr($line, 0, 5) != '<?php') {
429 $buffer = "<?php\n\n";
430 }
431 $first = FALSE;
432 // Check for constants.
433 if (substr($line, 0, 7) == 'define(') {
434 preg_match('/define\(\s*[\'"]([A-Z_-]+)[\'"]\s*,(.*?)\);/', $line, $variable);
435 if (in_array($variable[1], $keys)) {
436 $setting = $settings[$variable[1]];
437 $buffer .= str_replace($variable[2], " '" . $setting['value'] . "'", $line);
438 unset($settings[$variable[1]]);
439 unset($settings[$variable[2]]);
440 }
441 else {
442 $buffer .= $line;
443 }
444 }
445 // Check for variables.
446 elseif (substr($line, 0, 1) == '$') {
447 preg_match('/\$([^ ]*) /', $line, $variable);
448 if (in_array($variable[1], $keys)) {
449 // Write new value to settings.php in the following format:
450 // $'setting' = 'value'; // 'comment'
451 $setting = $settings[$variable[1]];
452 $buffer .= '$' . $variable[1] . " = " . var_export($setting['value'], TRUE) . ";" . (!empty($setting['comment']) ? ' // ' . $setting['comment'] . "\n" : "\n");
453 unset($settings[$variable[1]]);
454 }
455 else {
456 $buffer .= $line;
457 }
458 }
459 else {
460 $buffer .= $line;
461 }
462 }
463 fclose($fp);
464
465 // Add required settings that were missing from settings.php.
466 foreach ($settings as $setting => $data) {
467 if ($data['required']) {
468 $buffer .= "\$$setting = " . var_export($data['value'], TRUE) . ";\n";
469 }
470 }
471
472 $fp = fopen(DRUPAL_ROOT . '/' . $settings_file, 'w');
473 if ($fp && fwrite($fp, $buffer) === FALSE) {
474 throw new Exception(st('Failed to modify %settings, please verify the file permissions.', array('%settings' => $settings_file)));
475 }
476 }
477 else {
478 throw new Exception(st('Failed to open %settings, please verify the file permissions.', array('%settings' => $default_settings)));
479 }
480 }
481
482 /**
483 * Get list of all .install files.
484 *
485 * @param $module_list
486 * An array of modules to search for their .install files.
487 */
488 function drupal_get_install_files($module_list = array()) {
489 $installs = array();
490 foreach ($module_list as $module) {
491 $installs = array_merge($installs, drupal_system_listing('/' . $module . '.install$/', 'modules'));
492 }
493 return $installs;
494 }
495
496
497 /**
498 * Verify an install profile for installation.
499 *
500 * @param $install_state
501 * An array of information about the current installation state.
502 * @return
503 * The list of modules to install.
504 */
505 function drupal_verify_profile($install_state) {
506 $profile = $install_state['parameters']['profile'];
507 $locale = $install_state['parameters']['locale'];
508
509 include_once DRUPAL_ROOT . '/includes/file.inc';
510 include_once DRUPAL_ROOT . '/includes/common.inc';
511
512 $profile_file = DRUPAL_ROOT . "/profiles/$profile/$profile.profile";
513
514 if (!isset($profile) || !file_exists($profile_file)) {
515 throw new Exception(install_no_profile_error());
516 }
517 $info = $install_state['profile_info'];
518
519 // Get a list of modules that exist in Drupal's assorted subdirectories.
520 $present_modules = array();
521 foreach (drupal_system_listing('/\.module$/', 'modules', 'name', 0) as $present_module) {
522 $present_modules[] = $present_module->name;
523 }
524
525 // Verify that all of the profile's required modules are present.
526 $missing_modules = array_diff($info['dependencies'], $present_modules);
527
528 $requirements = array();
529
530 if (count($missing_modules)) {
531 $modules = array();
532 foreach ($missing_modules as $module) {
533 $modules[] = '<span class="admin-missing">' . drupal_ucfirst($module) . '</span>';
534 }
535 $requirements['required_modules'] = array(
536 'title' => st('Required modules'),
537 'value' => st('Required modules not found.'),
538 'severity' => REQUIREMENT_ERROR,
539 'description' => st('The following modules are required but were not found. Please move them into the appropriate modules subdirectory, such as <em>sites/all/modules</em>. Missing modules: !modules', array('!modules' => implode(', ', $modules))),
540 );
541 }
542 return $requirements;
543 }
544
545 /**
546 * Calls the install function for a given list of modules.
547 *
548 * @param $module_list
549 * The modules to install.
550 * @param $disable_modules_installed_hook
551 * Normally just testing wants to set this to TRUE.
552 */
553 function drupal_install_modules($module_list = array(), $disable_modules_installed_hook = FALSE) {
554 $files = system_get_module_data();
555 $module_list = array_flip(array_values($module_list));
556 do {
557 $moved = FALSE;
558 foreach ($module_list as $module => $weight) {
559 $file = $files[$module];
560 if (isset($file->info['dependencies']) && is_array($file->info['dependencies'])) {
561 foreach ($file->info['dependencies'] as $dependency) {
562 if (isset($module_list[$dependency]) && $module_list[$module] < $module_list[$dependency] +1) {
563 $module_list[$module] = $module_list[$dependency] +1;
564 $moved = TRUE;
565 }
566 }
567 }
568 }
569 } while ($moved);
570 asort($module_list);
571 $module_list = array_keys($module_list);
572 module_enable($module_list, $disable_modules_installed_hook);
573 }
574
575 /**
576 * Callback to install an individual install profile module.
577 *
578 * Used during installation to install modules one at a time and then
579 * enable them, or to install a number of modules at one time
580 * from admin/config/modules.
581 *
582 * @param $module
583 * The machine name of the module to install.
584 * @return
585 * TRUE if the module got installed.
586 */
587 function _drupal_install_module($module) {
588 if (drupal_get_installed_schema_version($module, TRUE) == SCHEMA_UNINSTALLED) {
589 module_load_install($module);
590 drupal_load('module', $module);
591 module_invoke($module, 'install');
592 $versions = drupal_get_schema_versions($module);
593 drupal_set_installed_schema_version($module, $versions ? max($versions) : SCHEMA_INSTALLED);
594 return TRUE;
595 }
596 }
597
598 /**
599 * Manually include all files for the active database.
600 *
601 * Because we have no registry yet, we need to manually include the
602 * necessary database include files.
603 */
604 function drupal_install_initialize_database() {
605 static $included = FALSE;
606
607 if (!$included) {
608 $connection_info = Database::getConnectionInfo();
609 $driver = $connection_info['default']['driver'];
610 require_once DRUPAL_ROOT . '/includes/database/query.inc';
611 require_once DRUPAL_ROOT . '/includes/database/select.inc';
612 require_once DRUPAL_ROOT . '/includes/database/schema.inc';
613 foreach (glob(DRUPAL_ROOT . '/includes/database/' . $driver . '/*.inc') as $include_file) {
614 require_once $include_file;
615 }
616 $included = TRUE;
617 }
618 }
619
620 /**
621 * Callback to install the system module.
622 *
623 * Separated from the installation of other modules so core system
624 * functions can be made available while other modules are installed.
625 */
626 function drupal_install_system() {
627 $system_path = dirname(drupal_get_filename('module', 'system', NULL));
628 require_once DRUPAL_ROOT . '/' . $system_path . '/system.install';
629 drupal_install_initialize_database();
630 module_invoke('system', 'install');
631
632 $system_versions = drupal_get_schema_versions('system');
633 $system_version = $system_versions ? max($system_versions) : SCHEMA_INSTALLED;
634 db_insert('system')
635 ->fields(array('filename', 'name', 'type', 'owner', 'status', 'schema_version'))
636 ->values(array(
637 'filename' => $system_path . '/system.module',
638 'name' => 'system',
639 'type' => 'module',
640 'owner' => '',
641 'status' => 1,
642 'schema_version' => $system_version
643 ))
644 ->execute();
645 // Now that we've installed things properly, bootstrap the full Drupal environment
646 drupal_bootstrap(DRUPAL_BOOTSTRAP_FULL);
647 system_get_module_data();
648 }
649
650 /**
651 * Calls the uninstall function and updates the system table for a given module.
652 *
653 * @param $module_list
654 * The modules to uninstall.
655 */
656 function drupal_uninstall_modules($module_list = array()) {
657 foreach ($module_list as $module) {
658 // First, retrieve all the module's menu paths from db.
659 drupal_load('module', $module);
660 $paths = module_invoke($module, 'menu');
661
662 // Uninstall the module.
663 module_load_install($module);
664 module_invoke($module, 'uninstall');
665 watchdog('system', '%module module uninstalled.', array('%module' => $module), WATCHDOG_INFO);
666
667 // Now remove the menu links for all paths declared by this module.
668 if (!empty($paths)) {
669 $paths = array_keys($paths);
670 // Clean out the names of load functions.
671 foreach ($paths as $index => $path) {
672 $parts = explode('/', $path, MENU_MAX_PARTS);
673 foreach ($parts as $k => $part) {
674 if (preg_match('/^%[a-z_]*$/', $part)) {
675 $parts[$k] = '%';
676 }
677 }
678 $paths[$index] = implode('/', $parts);
679 }
680
681 $result = db_select('menu_links')
682 ->fields('menu_links')
683 ->condition('router_path', $paths, 'IN')
684 ->condition('external', 0)
685 ->orderBy('depth')
686 ->execute();
687 // Remove all such items. Starting from those with the greatest depth will
688 // minimize the amount of re-parenting done by menu_link_delete().
689 foreach ($result as $item) {
690 _menu_delete_item($item, TRUE);
691 }
692 }
693
694 drupal_set_installed_schema_version($module, SCHEMA_UNINSTALLED);
695 }
696
697 if (!empty($module_list)) {
698 // Call hook_module_uninstall to let other modules act
699 module_invoke_all('modules_uninstalled', $module_list);
700 }
701 }
702
703 /**
704 * Verify the state of the specified file.
705 *
706 * @param $file
707 * The file to check for.
708 * @param $mask
709 * An optional bitmask created from various FILE_* constants.
710 * @param $type
711 * The type of file. Can be file (default), dir, or link.
712 * @return
713 * TRUE on success or FALSE on failure. A message is set for the latter.
714 */
715 function drupal_verify_install_file($file, $mask = NULL, $type = 'file') {
716 $return = TRUE;
717 // Check for files that shouldn't be there.
718 if (isset($mask) && ($mask & FILE_NOT_EXIST) && file_exists($file)) {
719 return FALSE;
720 }
721 // Verify that the file is the type of file it is supposed to be.
722 if (isset($type) && file_exists($file)) {
723 $check = 'is_' . $type;
724 if (!function_exists($check) || !$check($file)) {
725 $return = FALSE;
726 }
727 }
728
729 // Verify file permissions.
730 if (isset($mask)) {
731 $masks = array(FILE_EXIST, FILE_READABLE, FILE_WRITABLE, FILE_EXECUTABLE, FILE_NOT_READABLE, FILE_NOT_WRITABLE, FILE_NOT_EXECUTABLE);
732 foreach ($masks as $current_mask) {
733 if ($mask & $current_mask) {
734 switch ($current_mask) {
735 case FILE_EXIST:
736 if (!file_exists($file)) {
737 if ($type == 'dir') {
738 drupal_install_mkdir($file, $mask);
739 }
740 if (!file_exists($file)) {
741 $return = FALSE;
742 }
743 }
744 break;
745 case FILE_READABLE:
746 if (!is_readable($file) && !drupal_install_fix_file($file, $mask)) {
747 $return = FALSE;
748 }
749 break;
750 case FILE_WRITABLE:
751 if (!is_writable($file) && !drupal_install_fix_file($file, $mask)) {
752 $return = FALSE;
753 }
754 break;
755 case FILE_EXECUTABLE:
756 if (!is_executable($file) && !drupal_install_fix_file($file, $mask)) {
757 $return = FALSE;
758 }
759 break;
760 case FILE_NOT_READABLE:
761 if (is_readable($file) && !drupal_install_fix_file($file, $mask)) {
762 $return = FALSE;
763 }
764 break;
765 case FILE_NOT_WRITABLE:
766 if (is_writable($file) && !drupal_install_fix_file($file, $mask)) {
767 $return = FALSE;
768 }
769 break;
770 case FILE_NOT_EXECUTABLE:
771 if (is_executable($file) && !drupal_install_fix_file($file, $mask)) {
772 $return = FALSE;
773 }
774 break;
775 }
776 }
777 }
778 }
779 return $return;
780 }
781
782 /**
783 * Create a directory with specified permissions.
784 *
785 * @param $file
786 * The name of the directory to create;
787 * @param $mask
788 * The permissions of the directory to create.
789 * @param $message
790 * (optional) Whether to output messages. Defaults to TRUE.
791 * @return
792 * TRUE/FALSE whether or not the directory was successfully created.
793 */
794 function drupal_install_mkdir($file, $mask, $message = TRUE) {
795 $mod = 0;
796 $masks = array(FILE_READABLE, FILE_WRITABLE, FILE_EXECUTABLE, FILE_NOT_READABLE, FILE_NOT_WRITABLE, FILE_NOT_EXECUTABLE);
797 foreach ($masks as $m) {
798 if ($mask & $m) {
799 switch ($m) {
800 case FILE_READABLE:
801 $mod += 444;
802 break;
803 case FILE_WRITABLE:
804 $mod += 222;
805 break;
806 case FILE_EXECUTABLE:
807 $mod += 111;
808 break;
809 }
810 }
811 }
812
813 if (@mkdir($file, intval("0$mod", 8))) {
814 return TRUE;
815 }
816 else {
817 return FALSE;
818 }
819 }
820
821 /**
822 * Attempt to fix file permissions.
823 *
824 * The general approach here is that, because we do not know the security
825 * setup of the webserver, we apply our permission changes to all three
826 * digits of the file permission (i.e. user, group and all).
827 *
828 * To ensure that the values behave as expected (and numbers don't carry
829 * from one digit to the next) we do the calculation on the octal value
830 * using bitwise operations. This lets us remove, for example, 0222 from
831 * 0700 and get the correct value of 0500.
832 *
833 * @param $file
834 * The name of the file with permissions to fix.
835 * @param $mask
836 * The desired permissions for the file.
837 * @param $message
838 * (optional) Whether to output messages. Defaults to TRUE.
839 * @return
840 * TRUE/FALSE whether or not we were able to fix the file's permissions.
841 */
842 function drupal_install_fix_file($file, $mask, $message = TRUE) {
843 // If $file does not exist, fileperms() issues a PHP warning.
844 if (!file_exists($file)) {
845 return FALSE;
846 }
847
848 $mod = fileperms($file) & 0777;
849 $masks = array(FILE_READABLE, FILE_WRITABLE, FILE_EXECUTABLE, FILE_NOT_READABLE, FILE_NOT_WRITABLE, FILE_NOT_EXECUTABLE);
850
851 // FILE_READABLE, FILE_WRITABLE, and FILE_EXECUTABLE permission strings
852 // can theoretically be 0400, 0200, and 0100 respectively, but to be safe
853 // we set all three access types in case the administrator intends to
854 // change the owner of settings.php after installation.
855 foreach ($masks as $m) {
856 if ($mask & $m) {
857 switch ($m) {
858 case FILE_READABLE:
859 if (!is_readable($file)) {
860 $mod |= 0444;
861 }
862 break;
863 case FILE_WRITABLE:
864 if (!is_writable($file)) {
865 $mod |= 0222;
866 }
867 break;
868 case FILE_EXECUTABLE:
869 if (!is_executable($file)) {
870 $mod |= 0111;
871 }
872 break;
873 case FILE_NOT_READABLE:
874 if (is_readable($file)) {
875 $mod &= ~0444;
876 }
877 break;
878 case FILE_NOT_WRITABLE:
879 if (is_writable($file)) {
880 $mod &= ~0222;
881 }
882 break;
883 case FILE_NOT_EXECUTABLE:
884 if (is_executable($file)) {
885 $mod &= ~0111;
886 }
887 break;
888 }
889 }
890 }
891
892 // chmod() will work if the web server is running as owner of the file.
893 // If PHP safe_mode is enabled the currently executing script must also
894 // have the same owner.
895 if (@chmod($file, $mod)) {
896 return TRUE;
897 }
898 else {
899 return FALSE;
900 }
901 }
902
903
904 /**
905 * Send the user to a different installer page.
906 *
907 * This issues an on-site HTTP redirect. Messages (and errors) are erased.
908 *
909 * @param $path
910 * An installer path.
911 */
912 function install_goto($path) {
913 global $base_url;
914 header('Location: ' . $base_url . '/' . $path);
915 header('Cache-Control: no-cache'); // Not a permanent redirect.
916 exit();
917 }
918
919 /**
920 * Functional equivalent of t(), used when some systems are not available.
921 *
922 * Used during the install process, when database, theme, and localization
923 * system is possibly not yet available.
924 *
925 * @see t()
926 */
927 function st($string, $args = array()) {
928 static $locale_strings = NULL;
929 global $install_state;
930
931 if (!isset($locale_strings)) {
932 $locale_strings = array();
933 if (isset($install_state['parameters']['profile']) && isset($install_state['parameters']['locale'])) {
934 $filename = 'profiles/' . $install_state['parameters']['profile'] . '/translations/' . $install_state['parameters']['locale'] . '.po';
935 if (file_exists(DRUPAL_ROOT . '/' . $filename)) {
936 require_once DRUPAL_ROOT . '/includes/locale.inc';
937 $file = (object) array('filepath' => $filename);
938 _locale_import_read_po('mem-store', $file);
939 $locale_strings = _locale_import_one_string('mem-report');
940 }
941 }
942 }
943
944 require_once DRUPAL_ROOT . '/includes/theme.inc';
945 // Transform arguments before inserting them
946 foreach ($args as $key => $value) {
947 switch ($key[0]) {
948 // Escaped only
949 case '@':
950 $args[$key] = check_plain($value);
951 break;
952 // Escaped and placeholder
953 case '%':
954 default:
955 $args[$key] = '<em>' . check_plain($value) . '</em>';
956 break;
957 // Pass-through
958 case '!':
959 }
960 }
961 return strtr((!empty($locale_strings[$string]) ? $locale_strings[$string] : $string), $args);
962 }
963
964 /**
965 * Check an install profile's requirements.
966 *
967 * @param $profile
968 * Name of install profile to check.
969 * @return
970 * Array of the install profile's requirements.
971 */
972 function drupal_check_profile($profile) {
973 include_once DRUPAL_ROOT . '/includes/file.inc';
974
975 $profile_file = DRUPAL_ROOT . "/profiles/$profile/$profile.profile";
976
977 if (!isset($profile) || !file_exists($profile_file)) {
978 throw new Exception(install_no_profile_error());
979 }
980
981 $info = install_profile_info($profile);
982
983 // Get a list of all .install files.
984 $installs = drupal_get_install_files($info['dependencies']);
985
986 // Collect requirement testing results
987 $requirements = array();
988 foreach ($installs as $install) {
989 require_once DRUPAL_ROOT . '/' . $install->filepath;
990 $function = $install->name . '_requirements';
991 if (function_exists($function)) {
992 $requirements = array_merge($requirements, $function('install'));
993 }
994 }
995 return $requirements;
996 }
997
998 /**
999 * Extract highest severity from requirements array.
1000 *
1001 * @param $requirements
1002 * An array of requirements, in the same format as is returned by
1003 * hook_requirements().
1004 * @return
1005 * The highest severity in the array.
1006 */
1007 function drupal_requirements_severity(&$requirements) {
1008 $severity = REQUIREMENT_OK;
1009 foreach ($requirements as $requirement) {
1010 if (isset($requirement['severity'])) {
1011 $severity = max($severity, $requirement['severity']);
1012 }
1013 }
1014 return $severity;
1015 }
1016
1017 /**
1018 * Check a module's requirements.
1019 *
1020 * @param $module
1021 * Machine name of module to check.
1022 * @return
1023 * TRUE/FALSE depending on the requirements are in place.
1024 */
1025 function drupal_check_module($module) {
1026 // Include install file
1027 $install = drupal_get_install_files(array($module));
1028 if (isset($install[$module])) {
1029 require_once DRUPAL_ROOT . '/' . $install[$module]->filepath;
1030
1031 // Check requirements
1032 $requirements = module_invoke($module, 'requirements', 'install');
1033 if (is_array($requirements) && drupal_requirements_severity($requirements) == REQUIREMENT_ERROR) {
1034 // Print any error messages
1035 foreach ($requirements as $requirement) {
1036 if (isset($requirement['severity']) && $requirement['severity'] == REQUIREMENT_ERROR) {
1037 $message = $requirement['description'];
1038 if (isset($requirement['value']) && $requirement['value']) {
1039 $message .= ' (' . t('Currently using !item !version', array('!item' => $requirement['title'], '!version' => $requirement['value'])) . ')';
1040 }
1041 drupal_set_message($message, 'error');
1042 }
1043 }
1044 return FALSE;
1045 }
1046 }
1047 return TRUE;
1048 }
1049
1050 /**
1051 * Retrieve info about an install profile from its .info file.
1052 *
1053 * Information stored in the profile.info file:
1054 * - name: The real name of the install profile for display purposes.
1055 * - description: A brief description of the profile.
1056 * - dependencies: An array of shortnames of other modules this install profile requires.
1057 *
1058 * Example of .info file:
1059 * @verbatim
1060 * name = Drupal (minimal)
1061 * description = Create a Drupal site with only required modules enabled.
1062 * dependencies[] = block
1063 * dependencies[] = dblog
1064 * @endverbatim
1065 *
1066 * @param profile
1067 * Name of profile.
1068 * @param locale
1069 * Name of locale used (if any).
1070 * @return
1071 * The info array.
1072 */
1073 function install_profile_info($profile, $locale = 'en') {
1074 $cache = &drupal_static(__FUNCTION__, array());
1075
1076 if (!isset($cache[$profile])) {
1077 // Set defaults for module info.
1078 $defaults = array(
1079 'dependencies' => array(),
1080 'description' => '',
1081 'version' => NULL,
1082 'php' => DRUPAL_MINIMUM_PHP,
1083 );
1084 $info = drupal_parse_info_file("profiles/$profile/$profile.info") + $defaults;
1085 $info['dependencies'] = array_unique(array_merge(
1086 drupal_required_modules(),
1087 $info['dependencies'],
1088 ($locale != 'en' && !empty($locale) ? array('locale') : array()))
1089 );
1090 $cache[$profile] = $info;
1091 }
1092 return $cache[$profile];
1093 }
1094
1095 /**
1096 * Ensures the environment for a Drupal database on a predefined connection.
1097 *
1098 * This will run tasks that check that Drupal can perform all of the functions
1099 * on a database, that Drupal needs. Tasks include simple checks like CREATE
1100 * TABLE to database specfic functions like stored procedures and client
1101 * encoding.
1102 */
1103 function db_run_tasks($driver) {
1104 $task_class = 'DatabaseTasks_' . $driver;
1105 $DatabaseTasks = new $task_class();
1106 $DatabaseTasks->runTasks();
1107 return true;
1108 }
1109

Legend

Missed
lines code that were not excersized during program execution.
Covered
lines code were excersized during program execution.
Comment/non executable
Comment or non-executable line of code.
Dead
lines of code that according to xdebug could not be executed. This is counted as coverage code because in almost all cases it is code that runnable.