summaryrefslogtreecommitdiffstatshomepage
path: root/core/includes/module.inc
blob: 247aaaca165489f38e1b2587eaf8052f405d304a (plain) (blame)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
<?php

/**
 * @file
 * API for loading and interacting with Drupal modules.
 */


/**
 * Builds a list of bootstrap modules and enabled modules and themes.
 *
 * @param $type
 *   The type of list to return:
 *   - module_enabled: All enabled modules.
 *   - bootstrap: All enabled modules required for bootstrap.
 *   - theme: All themes.
 *
 * @return
 *   An associative array of modules or themes, keyed by name. For $type
 *   'bootstrap' and 'module_enabled', the array values equal the keys.
 *   For $type 'theme', the array values are objects representing the
 *   respective database row, with the 'info' property already unserialized.
 *
 * @see list_themes()
 *
 * @todo There are too many layers/levels of caching involved for system_list()
 *   data. Consider to add a \Drupal::config($name, $cache = TRUE) argument to allow
 *   callers like system_list() to force-disable a possible configuration
 *   storage controller cache or some other way to circumvent it/take it over.
 */
function system_list($type) {
  $lists = &drupal_static(__FUNCTION__);
  if ($cached = cache('bootstrap')->get('system_list')) {
    $lists = $cached->data;
  }
  else {
    $lists = array(
      'theme' => array(),
      'filepaths' => array(),
    );
    // Build a list of themes.
    $enabled_themes = (array) \Drupal::config('system.theme')->get('enabled');
    // @todo Themes include all themes, including disabled/uninstalled. This
    //   system.theme.data state will go away entirely as soon as themes have
    //   a proper installation status.
    // @see http://drupal.org/node/1067408
    $theme_data = \Drupal::state()->get('system.theme.data');
    if (empty($theme_data)) {
      // @todo: system_list() may be called from _drupal_bootstrap_code(), in
      // which case system.module is not loaded yet.
      // Prevent a filesystem scan in drupal_load() and include it directly.
      // @see http://drupal.org/node/1067408
      require_once DRUPAL_ROOT . '/core/modules/system/system.module';
      $theme_data = system_rebuild_theme_data();
    }
    foreach ($theme_data as $name => $theme) {
      $theme->status = (int) isset($enabled_themes[$name]);
      $lists['theme'][$name] = $theme;
      // Build a list of filenames so drupal_get_filename can use it.
      if (isset($enabled_themes[$name])) {
        $lists['filepaths'][] = array(
          'type' => 'theme',
          'name' => $name,
          'filepath' => $theme->filename,
        );
      }
    }
    // @todo Move into list_themes(). Read info for a particular requested
    //   theme from state instead.
    foreach ($lists['theme'] as $key => $theme) {
      if (!empty($theme->info['base theme'])) {
        // Make a list of the theme's base themes.
        require_once __DIR__ . '/theme.inc';
        $lists['theme'][$key]->base_themes = drupal_find_base_themes($lists['theme'], $key);
        // Don't proceed if there was a problem with the root base theme.
        if (!current($lists['theme'][$key]->base_themes)) {
          continue;
        }
        // Determine the root base theme.
        $base_key = key($lists['theme'][$key]->base_themes);
        // Add to the list of sub-themes for each of the theme's base themes.
        foreach (array_keys($lists['theme'][$key]->base_themes) as $base_theme) {
          $lists['theme'][$base_theme]->sub_themes[$key] = $lists['theme'][$key]->info['name'];
        }
        // Add the base theme's theme engine info.
        $lists['theme'][$key]->info['engine'] = $lists['theme'][$base_key]->info['engine'];
      }
      else {
        // A plain theme is its own base theme.
        $base_key = $key;
      }
      // Set the theme engine prefix.
      $lists['theme'][$key]->prefix = ($lists['theme'][$key]->info['engine'] == 'theme') ? $base_key : $lists['theme'][$key]->info['engine'];
    }
    cache('bootstrap')->set('system_list', $lists);
  }
  // To avoid a separate database lookup for the filepath, prime the
  // drupal_get_filename() static cache with all enabled modules and themes.
  foreach ($lists['filepaths'] as $item) {
    system_register($item['type'], $item['name'], $item['filepath']);
  }

  return $lists[$type];
}

/**
 * Resets all system_list() caches.
 */
function system_list_reset() {
  drupal_static_reset('system_list');
  drupal_static_reset('system_rebuild_module_data');
  drupal_static_reset('list_themes');
  cache('bootstrap')->delete('system_list');
  cache()->delete('system_info');
  // Remove last known theme data state.
  // This causes system_list() to call system_rebuild_theme_data() on its next
  // invocation. When enabling a module that implements hook_system_info_alter()
  // to inject a new (testing) theme or manipulate an existing theme, then that
  // will cause system_list_reset() to be called, but theme data is not
  // necessarily rebuilt afterwards.
  // @todo Obsolete with proper installation status for themes.
  \Drupal::state()->delete('system.theme.data');
}

/**
 * Registers an extension in runtime registries for execution.
 *
 * @param string $type
 *   The extension type; e.g., 'module' or 'theme'.
 * @param string $name
 *   The internal name of the extension; e.g., 'node'.
 * @param string $uri
 *   The relative URI of the primary extension file; e.g.,
 *   'core/modules/node/node.module'.
 */
function system_register($type, $name, $uri) {
  drupal_get_filename($type, $name, $uri);
  drupal_classloader_register($name, dirname($uri));
}

/**
 * Loads a module's installation hooks.
 *
 * @param $module
 *   The name of the module (without the .module extension).
 *
 * @return
 *   The name of the module's install file, if successful; FALSE otherwise.
 */
function module_load_install($module) {
  // Make sure the installation API is available
  include_once __DIR__ . '/install.inc';

  return module_load_include('install', $module);
}

/**
 * Loads a module include file.
 *
 * Examples:
 * @code
 *   // Load node.admin.inc from the node module.
 *   module_load_include('inc', 'node', 'node.admin');
 *   // Load content_types.inc from the node module.
 *   module_load_include('inc', 'node', 'content_types');
 * @endcode
 *
 * Do not use this function to load an install file, use module_load_install()
 * instead. Do not use this function in a global context since it requires
 * Drupal to be fully bootstrapped, use require_once DRUPAL_ROOT . '/path/file'
 * instead.
 *
 * @param $type
 *   The include file's type (file extension).
 * @param $module
 *   The module to which the include file belongs.
 * @param $name
 *   (optional) The base file name (without the $type extension). If omitted,
 *   $module is used; i.e., resulting in "$module.$type" by default.
 *
 * @return
 *   The name of the included file, if successful; FALSE otherwise.
 *
 * @todo The module_handler service has a loadInclude() method which performs
 *   this same task but only for enabled modules. Figure out a way to move this
 *   functionality entirely into the module_handler while keeping the ability to
 *   load the files of disabled modules.
 */
function module_load_include($type, $module, $name = NULL) {
  if (!isset($name)) {
    $name = $module;
  }

  if (function_exists('drupal_get_path')) {
    $file = DRUPAL_ROOT . '/' . drupal_get_path('module', $module) . "/$name.$type";
    if (is_file($file)) {
      require_once $file;
      return $file;
    }
  }
  return FALSE;
}

/**
 * Installs a given list of modules.
 *
 * @deprecated as of Drupal 8.0. Use
 *  \Drupal::moduleHandler()->install($module_list, $enable_dependencies = TRUE)
 */
function module_install($module_list, $enable_dependencies = TRUE) {
  return \Drupal::moduleHandler()->install($module_list, $enable_dependencies);
}

/**
 * Installs a given list of modules.
 *
 * @deprecated as of Drupal 8.0. Use
 *   \Drupal::moduleHandler()->install($module_list, $enable_dependencies = TRUE).
 */
function module_uninstall($module_list = array(), $uninstall_dependents = TRUE) {
  return \Drupal::moduleHandler()->uninstall($module_list, $uninstall_dependents);
}

/**
 * @defgroup hooks Hooks
 * @{
 * Allow modules to interact with the Drupal core.
 *
 * Drupal's module system is based on the concept of "hooks". A hook is a PHP
 * function that is named foo_bar(), where "foo" is the name of the module
 * (whose filename is thus foo.module) and "bar" is the name of the hook. Each
 * hook has a defined set of parameters and a specified result type.
 *
 * To extend Drupal, a module need simply implement a hook. When Drupal wishes
 * to allow intervention from modules, it determines which modules implement a
 * hook and calls that hook in all enabled modules that implement it.
 *
 * The available hooks to implement are explained here in the Hooks section of
 * the developer documentation. The string "hook" is used as a placeholder for
 * the module name in the hook definitions. For example, if the module file is
 * called example.module, then hook_help() as implemented by that module would
 * be defined as example_help().
 *
 * The example functions included are not part of the Drupal core, they are
 * just models that you can modify. Only the hooks implemented within modules
 * are executed when running Drupal.
 *
 * @see themeable
 * @see callbacks
 *
 * @} End of "defgroup hooks".
 */

 /**
  * @defgroup callbacks Callbacks
  * @{
  * Callback function signatures.
  *
  * Drupal's API sometimes uses callback functions to allow you to define how
  * some type of processing happens. A callback is a function with a defined
  * signature, which you define in a module. Then you pass the function name as
  * a parameter to a Drupal API function or return it as part of a hook
  * implementation return value, and your function is called at an appropriate
  * time. For instance, when setting up batch processing you might need to
  * provide a callback function for each processing step and/or a callback for
  * when processing is finished; you would do that by defining these functions
  * and passing their names into the batch setup function.
  *
  * Callback function signatures, like hook definitions, are described by
  * creating and documenting dummy functions in a *.api.php file; normally, the
  * dummy callback function's name should start with "callback_", and you should
  * document the parameters and return value and provide a sample function body.
  * Then your API documentation can refer to this callback function in its
  * documentation. A user of your API can usually name their callback function
  * anything they want, although a standard name would be to replace "callback_"
  * with the module name.
  *
  * @see hooks
  * @see themeable
  *
  * @}
  */

/**
 * Returns an array of modules required by core.
 */
function drupal_required_modules() {
  $files = drupal_system_listing('/^' . DRUPAL_PHP_FUNCTION_PATTERN . '\.info.yml$/', 'modules');
  $required = array();

  // An installation profile is required and one must always be loaded.
  $required[] = drupal_get_profile();

  foreach ($files as $name => $file) {
    $info = drupal_parse_info_file($file->uri);
    if (!empty($info) && !empty($info['required']) && $info['required']) {
      $required[] = $name;
    }
  }

  return $required;
}

/**
 * Sets weight of a particular module.
 *
 * The weight of uninstalled modules cannot be changed.
 *
 * @param string $module
 *   The name of the module (without the .module extension).
 * @param int $weight
 *   An integer representing the weight of the module.
 */
function module_set_weight($module, $weight) {
  // Update the module weight in the config file that contains it.
  $module_config = \Drupal::config('system.module');
  if ($module_config->get("enabled.$module") !== NULL) {
    $module_config
      ->set("enabled.$module", $weight)
      ->set('enabled', module_config_sort($module_config->get('enabled')))
      ->save();

    // Prepare the new module list, sorted by weight, including filenames.
    // @see \Drupal\Core\Extension\ModuleHandler::install()
    $module_handler = \Drupal::moduleHandler();
    $current_module_filenames = $module_handler->getModuleList();
    $current_modules = array_fill_keys(array_keys($current_module_filenames), 0);
    $current_modules = module_config_sort(array_merge($current_modules, $module_config->get('enabled')));
    $module_filenames = array();
    foreach ($current_modules as $name => $weight) {
      $module_filenames[$name] = $current_module_filenames[$name];
    }
    // Update the module list in the extension handler.
    $module_handler->setModuleList($module_filenames);
    return;
  }
}

/**
 * Sorts the configured list of enabled modules.
 *
 * The list of enabled modules is expected to be ordered by weight and name.
 * The list is always sorted on write to avoid the overhead on read.
 *
 * @param array $data
 *   An array of module configuration data.
 *
 * @return array
 *   An array of module configuration data sorted by weight and name.
 */
function module_config_sort($data) {
  // PHP array sorting functions such as uasort() do not work with both keys and
  // values at the same time, so we achieve weight and name sorting by computing
  // strings with both information concatenated (weight first, name second) and
  // use that as a regular string sort reference list via array_multisort(),
  // compound of "[sign-as-integer][padded-integer-weight][name]"; e.g., given
  // two modules and weights (spaces added for clarity):
  // - Block with weight -5: 0 0000000000000000005 block
  // - Node  with weight  0: 1 0000000000000000000 node
  $sort = array();
  foreach ($data as $name => $weight) {
    // Prefix negative weights with 0, positive weights with 1.
    // +/- signs cannot be used, since + (ASCII 43) is before - (ASCII 45).
    $prefix = (int) ($weight >= 0);
    // The maximum weight is PHP_INT_MAX, so pad all weights to 19 digits.
    $sort[] = $prefix . sprintf('%019d', abs($weight)) . $name;
  }
  array_multisort($sort, SORT_STRING, $data);
  return $data;
}