system.module 58.9 KB
Newer Older
Dries's avatar
 
Dries committed
1 2
<?php

Dries's avatar
 
Dries committed
3 4 5 6 7
/**
 * @file
 * Configuration system that lets administrators modify the workings of the site.
 */

8
use Drupal\Component\Render\PlainTextOutput;
9
use Drupal\Component\Utility\UrlHelper;
10
use Drupal\Core\Asset\AttachedAssetsInterface;
11
use Drupal\Core\Cache\Cache;
12
use Drupal\Core\Database\Query\AlterableInterface;
13
use Drupal\Core\Extension\Extension;
14
use Drupal\Core\Extension\ExtensionDiscovery;
15
use Drupal\Core\Form\FormStateInterface;
16
use Drupal\Core\KeyValueStore\KeyValueDatabaseExpirableFactory;
17
use Drupal\Core\PageCache\RequestPolicyInterface;
18
use Drupal\Core\PhpStorage\PhpStorageFactory;
19
use Drupal\Core\Routing\RouteMatchInterface;
20
use Drupal\Core\Language\LanguageInterface;
21
use Drupal\Core\Menu\MenuTreeParameters;
22
use Drupal\Core\Extension\ModuleHandler;
23
use Drupal\Core\Url;
24
use Drupal\Core\Block\BlockPluginInterface;
25
use Drupal\user\UserInterface;
26
use Symfony\Component\HttpFoundation\RedirectResponse;
27
use GuzzleHttp\Exception\RequestException;
28

29 30 31
/**
 * New users will be set to the default time zone at registration.
 */
32
const DRUPAL_USER_TIMEZONE_DEFAULT = 0;
33 34 35 36

/**
 * New users will get an empty time zone at registration.
 */
37
const DRUPAL_USER_TIMEZONE_EMPTY = 1;
38 39 40 41

/**
 * New users will select their own timezone at registration.
 */
42
const DRUPAL_USER_TIMEZONE_SELECT = 2;
43

44
/**
45 46
 * Disabled option on forms and settings
 */
47
const DRUPAL_DISABLED = 0;
48 49 50 51

/**
 * Optional option on forms and settings
 */
52
const DRUPAL_OPTIONAL = 1;
53 54 55 56

/**
 * Required option on forms and settings
 */
57
const DRUPAL_REQUIRED = 2;
58

59
/**
60
 * Return only visible regions.
61 62
 *
 * @see system_region_list()
63
 */
64
const REGIONS_VISIBLE = 'visible';
65 66

/**
67
 * Return all regions.
68 69
 *
 * @see system_region_list()
70
 */
71
const REGIONS_ALL = 'all';
72

Dries's avatar
 
Dries committed
73
/**
74
 * Implements hook_help().
Dries's avatar
 
Dries committed
75
 */
76
function system_help($route_name, RouteMatchInterface $route_match) {
77 78
  switch ($route_name) {
    case 'help.page.system':
79 80
      $output = '';
      $output .= '<h3>' . t('About') . '</h3>';
81
      $output .= '<p>' . t('The System module is integral to the site: it provides user interfaces for many core systems and settings, as well as the basic administrative menu structure. For more information, see the <a href=":system">online documentation for the System module</a>.', array(':system' => 'https://www.drupal.org/documentation/modules/system')) . '</p>';
82 83 84
      $output .= '<h3>' . t('Uses') . '</h3>';
      $output .= '<dl>';
      $output .= '<dt>' . t('Managing modules') . '</dt>';
85
      $output .= '<dd>' . t('Users with appropriate permission can install and uninstall modules from the <a href=":modules">Extend page</a>. Depending on which distribution or installation profile you choose when you install your site, several modules are installed and others are provided but not installed. Each module provides a discrete set of features; modules may be installed or uninstalled depending on the needs of the site. Many additional modules contributed by members of the Drupal community are available for download from the <a href=":drupal-modules">Drupal.org module page</a>. Note that uninstalling a module is a destructive action: when you uninstall a module, you will permanently lose all data connected to the module.', array(':modules' => \Drupal::url('system.modules_list'), ':drupal-modules' => 'https://www.drupal.org/project/modules')) . '</dd>';
86
      $output .= '<dt>' . t('Managing themes') . '</dt>';
87
      $output .= '<dd>' . t('Users with appropriate permission can install and uninstall themes on the <a href=":themes">Appearance page</a>. Themes determine the design and presentation of your site. Depending on which distribution or installation profile you choose when you install your site, a default theme is installed, and possibly a different theme for administration pages. Other themes are provided but not installed, and additional contributed themes are available at the <a href=":drupal-themes">Drupal.org theme page</a>.', array(':themes' => \Drupal::url('system.themes_page'), ':drupal-themes' => 'https://www.drupal.org/project/themes')) . '</dd>';
88
      $output .= '<dt>' . t('Disabling drag-and-drop functionality') . '</dt>';
89 90
      $output .= '<dd>' . t('The default drag-and-drop user interface for ordering tables in the administrative interface presents a challenge for some users, including users of screen readers and other assistive technology. The drag-and-drop interface can be disabled in a table by clicking a link labeled "Show row weights" above the table. The replacement interface allows users to order the table by choosing numerical weights instead of dragging table rows.') . '</dd>';
      $output .= '<dt>' . t('Configuring basic site settings') . '</dt>';
91
      $output .= '<dd>' . t('The System module provides pages for managing basic site configuration, including <a href=":date-time-settings">Date and time formats</a> and basic <a href=":site-info">Site information</a> (site name, email address to send mail from, home page, and error pages). Additional configuration pages are listed on the main <a href=":config">Configuration page</a>.', array(':date-time-settings' => \Drupal::url('entity.date_format.collection'), ':site-info' => \Drupal::url('system.site_information_settings'), ':config' => \Drupal::url('system.admin_config'))) . '</dd>';
92 93
      $output .= '<dt>' . t('Checking site status') . '</dt>';
      $output .= '<dd>' . t('The <a href=":status">Status report</a> provides an overview of the configuration, status, and health of your site. Review this report to make sure there are not any problems to address, and to find information about the software your site and web server are using.', array(':status' => \Drupal::url('system.status'))) . '</dd>';
94
      $output .= '<dt>' . t('Using maintenance mode') . '</dt>';
95
      $output .= '<dd>' . t('When you are performing site maintenance, you can prevent non-administrative users (including anonymous visitors) from viewing your site by putting it in <a href=":maintenance-mode">Maintenance mode</a>. This will prevent unauthorized users from making changes to the site while you are performing maintenance, or from seeing a broken site while updates are in progress.', array(':maintenance-mode' => \Drupal::url('system.site_maintenance_mode'))) . '</dd>';
96
      $output .= '<dt>' . t('Configuring for performance') . '</dt>';
97
      $output .= '<dd>' . t('On the <a href=":performance-page">Performance page</a>, the site can be configured to aggregate CSS and JavaScript files, making the total request size smaller. Note that, for small- to medium-sized websites, the <a href=":page-cache">Internal Page Cache module</a> should be installed so that pages are efficiently cached and reused for anonymous users. Finally, for websites of all sizes, the <a href=":dynamic-page-cache">Dynamic Page Cache module</a> should also be installed so that the non-personalized parts of pages are efficiently cached (for all users).', array(':performance-page' => \Drupal::url('system.performance_settings'), ':page-cache' => (\Drupal::moduleHandler()->moduleExists('page_cache')) ? \Drupal::url('help.page', array('name' => 'page_cache')) : '#', ':dynamic-page-cache' => (\Drupal::moduleHandler()->moduleExists('dynamic_page_cache')) ? \Drupal::url('help.page', array('name' => 'dynamic_page_cache')) : '#')) . '</dd>';
98
      $output .= '<dt>' . t('Configuring cron') . '</dt>';
99
      $output .= '<dd>' . t('In order for the site and its modules to continue to operate well, a set of routine administrative operations must run on a regular basis; these operations are known as <em>cron</em> tasks. On the <a href=":cron">Cron page</a>, you can configure cron to run periodically as part of normal page requests, or you can turn this off and trigger cron from an outside process on your web server. You can verify the status of cron tasks by visiting the <a href=":status">Status report page</a>. For more information, see the <a href=":handbook">online documentation for configuring cron jobs</a>.', array(':status' => \Drupal::url('system.status'), ':handbook' => 'https://www.drupal.org/cron', ':cron' => \Drupal::url('system.cron_settings'))) . '</dd>';
100
      $output .= '<dt>' . t('Configuring the file system') . '</dt>';
101
      $output .= '<dd>' . t('Your site has several file directories, which are used to store and process uploaded and generated files. The <em>public</em> file directory, which is configured in your settings.php file, is the default place for storing uploaded files. Links to files in this directory contain the direct file URL, so when the files are requested, the web server will send them directly without invoking your site code. This means that the files can be downloaded by anyone with the file URL, so requests are not access-controlled but they are efficient. The <em>private</em> file directory, also configured in your settings.php file and ideally located outside the site web root, is access controlled. Links to files in this directory are not direct, so requests to these files are mediated by your site code. This means that your site can check file access permission for each file before deciding to fulfill the request, so the requests are more secure, but less efficient. You should only use the private storage for files that need access control, not for files like your site logo and background images used on every page. The <em>temporary</em> file directory is used internally by your site code for various operations, and is configured on the <a href=":file-system">File system settings</a> page. You can also see the configured public and private file directories on this page, and choose whether public or private should be the default for uploaded files.', array(':file-system' => \Drupal::url('system.file_system_settings'))) . '</dd>';
102
      $output .= '<dt>' . t('Configuring the image toolkit') . '</dt>';
103
      $output .= '<dd>' . t('On the <a href=":toolkit">Image toolkit page</a>, you can select and configure the PHP toolkit used to manipulate images. Depending on which distribution or installation profile you choose when you install your site, the GD2 toolkit and possibly others are included; other toolkits may be provided by contributed modules.', array(':toolkit' => \Drupal::url('system.image_toolkit_settings'))) . '</dd>';
104
      $output .= '</dl>';
105
      return $output;
106 107

    case 'system.admin_index':
108
      return '<p>' . t('This page shows you all available administration tasks for each module.') . '</p>';
109 110

    case 'system.themes_page':
111
      $output = '<p>' . t('Set and configure the default theme for your website.  Alternative <a href=":themes">themes</a> are available.', array(':themes' => 'https://www.drupal.org/project/themes')) . '</p>';
112
      if (\Drupal::moduleHandler()->moduleExists('block')) {
113
        $output .= '<p>' . t('You can place blocks for each theme on the <a href=":blocks">block layout</a> page.', array(':blocks' => \Drupal::url('block.admin_display'))) . '</p>';
114
      }
115
      return $output;
116 117

    case 'system.theme_settings_theme':
118
      $theme_list = \Drupal::service('theme_handler')->listInfo();
119
      $theme = $theme_list[$route_match->getParameter('theme')];
120
      return '<p>' . t('These options control the display settings for the %name theme. When your site is displayed using this theme, these settings will be used.', array('%name' => $theme->info['name'])) . '</p>';
121 122

    case 'system.theme_settings':
123
      return '<p>' . t('These options control the default display settings for your entire site, across all themes. Unless they have been overridden by a specific theme, these settings will be used.') . '</p>';
124 125

    case 'system.modules_list':
126
      $output = '<p>' . t('Download additional <a href=":modules">contributed modules</a> to extend your site\'s functionality.', array(':modules' => 'https://www.drupal.org/project/modules')) . '</p>';
127
      if (!\Drupal::moduleHandler()->moduleExists('update')) {
128
        $output .= '<p>' . t('Regularly review available updates to maintain a secure and current site. Always run the <a href=":update-php">update script</a> each time a module is updated. Enable the <a href=":update-manager">Update Manager module</a> to update and install modules and themes.', array(':update-php' => \Drupal::url('system.db_update'), ':update-manager' => \Drupal::url('system.modules_list', [], ['fragment' => 'module-update']))) . '</p>';
129
      }
130
      return $output;
131 132

    case 'system.modules_uninstall':
133
      return '<p>' . t('The uninstall process removes all data related to a module.') . '</p>';
134

135
    case 'entity.block.edit_form':
136
      if (($block = $route_match->getParameter('block')) && $block->getPluginId() == 'system_powered_by_block') {
137
        return '<p>' . t('The <em>Powered by Drupal</em> block is an optional link to the home page of the Drupal project. While there is absolutely no requirement that sites feature this link, it may be used to show support for Drupal.') . '</p>';
138
      }
139
      break;
140 141

    case 'block.admin_add':
142
      if ($route_match->getParameter('plugin_id') == 'system_powered_by_block') {
143 144 145 146 147
        return '<p>' . t('The <em>Powered by Drupal</em> block is an optional link to the home page of the Drupal project. While there is absolutely no requirement that sites feature this link, it may be used to show support for Drupal.') . '</p>';
      }
      break;

    case 'system.site_maintenance_mode':
148
      if (\Drupal::currentUser()->id() == 1) {
149
        return '<p>' . t('Use maintenance mode when making major updates, particularly if the updates could disrupt visitors or the update process. Examples include upgrading, importing or exporting content, modifying a theme, modifying content types, and making backups.') . '</p>';
150
      }
151
      break;
152 153

    case 'system.status':
154
      return '<p>' . t("Here you can find a short overview of your site's parameters as well as any problems detected with your installation. It may be useful to copy and paste this information into support requests filed on Drupal.org's support forums and project issue queues. Before filing a support request, ensure that your web server meets the <a href=\":system-requirements\">system requirements.</a>", array(':system-requirements' => 'https://www.drupal.org/requirements')) . '</p>';
155
  }
Dries's avatar
 
Dries committed
156 157
}

158
/**
159
 * Implements hook_theme().
160
 */
161
function system_theme() {
162
  return array_merge(drupal_common_theme(), array(
163
    // Normally theme suggestion templates are only picked up when they are in
164 165
    // themes. We explicitly define theme suggestions here so that the block
    // templates in core/modules/system/templates are picked up.
166
    'block__system_branding_block' => array(
167
      'render element' => 'elements',
168 169
      'base hook' => 'block',
    ),
170 171 172
    'block__system_messages_block' => array(
      'base hook' => 'block',
    ),
173 174 175 176
    'block__system_menu_block' => array(
      'render element' => 'elements',
      'base hook' => 'block',
    ),
177
    'system_themes_page' => array(
178
      'variables' => array(
179 180
        'theme_groups' => array(),
        'theme_group_titles' => array(),
181
      ),
182
      'file' => 'system.admin.inc',
183
    ),
184
    'system_config_form' => array(
185
      'render element' => 'form',
186 187
    ),
    'confirm_form' => array(
188
      'render element' => 'form',
189
    ),
190
    'system_modules_details' => array(
191
      'render element' => 'form',
192
      'file' => 'system.admin.inc',
193 194
    ),
    'system_modules_uninstall' => array(
195
      'render element' => 'form',
196
      'file' => 'system.admin.inc',
197 198
    ),
    'status_report' => array(
199
      'variables' => array('requirements' => NULL),
200
      'file' => 'system.admin.inc',
201 202
    ),
    'admin_page' => array(
203
      'variables' => array('blocks' => NULL),
204
      'file' => 'system.admin.inc',
205 206
    ),
    'admin_block' => array(
207
      'variables' => array('block' => NULL),
208
      'file' => 'system.admin.inc',
209 210
    ),
    'admin_block_content' => array(
211
      'variables' => array('content' => NULL),
212
      'file' => 'system.admin.inc',
213
    ),
214
    'system_admin_index' => array(
215
      'variables' => array('menu_items' => NULL),
216
      'file' => 'system.admin.inc',
217 218 219
    ),
  ));
}
220

221
/**
222
 * Implements hook_hook_info().
223 224 225 226 227
 */
function system_hook_info() {
  $hooks['token_info'] = array(
    'group' => 'tokens',
  );
228 229 230
  $hooks['token_info_alter'] = array(
    'group' => 'tokens',
  );
231 232 233
  $hooks['tokens'] = array(
    'group' => 'tokens',
  );
234 235 236 237
  $hooks['tokens_alter'] = array(
    'group' => 'tokens',
  );

238 239 240
  return $hooks;
}

241 242 243 244
/**
 * Implements hook_theme_suggestions_HOOK().
 */
function system_theme_suggestions_html(array $variables) {
245 246 247 248
  if (\Drupal::service('path.matcher')->isFrontPage()) {
    $path_args = [''];
  }
  else {
249
    $path_args = explode('/', ltrim(\Drupal::service('path.current')->getPath(), '/'));
250
  }
251
  return theme_get_suggestions($path_args, 'html');
252 253 254 255 256 257
}

/**
 * Implements hook_theme_suggestions_HOOK().
 */
function system_theme_suggestions_page(array $variables) {
258 259 260 261 262 263
  if (\Drupal::service('path.matcher')->isFrontPage()) {
    $path_args = [''];
  }
  else {
    $path_args = explode('/', Url::fromRoute('<current>')->getInternalPath());
  }
264
  return theme_get_suggestions($path_args, 'page');
265 266 267 268 269 270 271 272 273 274 275 276
}

/**
 * Implements hook_theme_suggestions_HOOK().
 */
function system_theme_suggestions_maintenance_page(array $variables) {
  $suggestions = array();

  // Dead databases will show error messages so supplying this template will
  // allow themers to override the page and the content completely.
  $offline = defined('MAINTENANCE_MODE');
  try {
277
    \Drupal::service('path.matcher')->isFrontPage();
278 279 280 281 282 283 284 285 286 287 288 289 290 291 292 293 294 295 296 297 298 299 300
  }
  catch (Exception $e) {
    // The database is not yet available.
    $offline = TRUE;
  }
  if ($offline) {
    $suggestions[] = 'maintenance_page__offline';
  }

  return $suggestions;
}

/**
 * Implements hook_theme_suggestions_HOOK().
 */
function system_theme_suggestions_region(array $variables) {
  $suggestions = array();
  if (!empty($variables['elements']['#region'])) {
    $suggestions[] = 'region__' . $variables['elements']['#region'];
  }
  return $suggestions;
}

301 302 303 304 305 306 307 308 309 310 311 312 313 314 315 316
/**
 * Implements hook_theme_suggestions_HOOK().
 */
function system_theme_suggestions_field(array $variables) {
  $suggestions = array();
  $element = $variables['element'];

  $suggestions[] = 'field__' . $element['#field_type'];
  $suggestions[] = 'field__' . $element['#field_name'];
  $suggestions[] = 'field__' . $element['#entity_type'] . '__' . $element['#bundle'];
  $suggestions[] = 'field__' . $element['#entity_type'] . '__' . $element['#field_name'];
  $suggestions[] = 'field__' . $element['#entity_type'] . '__' . $element['#field_name'] . '__' . $element['#bundle'];

  return $suggestions;
}

317
/**
318 319 320
 * @defgroup authorize Authorized operations
 * @{
 * Functions to run operations with elevated privileges via authorize.php.
321
 *
322 323 324 325 326 327 328 329 330 331 332 333 334 335 336 337 338 339 340 341
 * Because of the Update manager functionality included in Drupal core, there
 * is a mechanism for running operations with elevated file system privileges,
 * the top-level authorize.php script. This script runs at a reduced Drupal
 * bootstrap level so that it is not reliant on the entire site being
 * functional. The operations use a FileTransfer class to manipulate code
 * installed on the system as the user that owns the files, not the user that
 * the httpd is running as.
 *
 * The first setup is to define a callback function that should be authorized
 * to run with the elevated privileges. This callback should take a
 * FileTransfer as its first argument, although you can define an array of
 * other arguments it should be invoked with. The callback should be placed in
 * a separate .inc file that will be included by authorize.php.
 *
 * To run the operation, certain data must be saved into the SESSION, and then
 * the flow of control should be redirected to the authorize.php script. There
 * are two ways to do this, either to call system_authorized_run() directly,
 * or to call system_authorized_init() and then redirect to authorize.php,
 * using the URL from system_authorized_get_url(). Redirecting yourself is
 * necessary when your authorized operation is being triggered by a form
342
 * submit handler, since calling redirecting in a submit handler is a bad
343
 * idea, and you should instead use $form_state->setRedirect().
344 345 346 347 348 349 350 351 352 353
 *
 * Once the SESSION is setup for the operation and the user is redirected to
 * authorize.php, they will be prompted for their connection credentials (core
 * provides FTP and SSH by default, although other connection classes can be
 * added via contributed modules). With valid credentials, authorize.php will
 * instantiate the appropriate FileTransfer object, and then invoke the
 * desired operation passing in that object. The authorize.php script can act
 * as a Batch API processing page, if the operation requires a batch.
 *
 * @see authorize.php
354
 * @see \Drupal\Core\FileTransfer\FileTransfer
355
 * @see hook_filetransfer_info()
356 357 358 359 360
 */

/**
 * Setup a given callback to run via authorize.php with elevated privileges.
 *
361 362 363 364 365 366 367 368 369
 * To use authorize.php, certain variables must be stashed into $_SESSION. This
 * function sets up all the necessary $_SESSION variables. The calling function
 * should then redirect to authorize.php, using the full path returned by
 * system_authorized_get_url(). That initiates the workflow that will eventually
 * lead to the callback being invoked. The callback will be invoked at a low
 * bootstrap level, without all modules being invoked, so it needs to be careful
 * not to assume any code exists. Example (system_authorized_run()):
 * @code
 *   system_authorized_init($callback, $file, $arguments, $page_title);
370
 *   return new RedirectResponse(system_authorized_get_url()->toString());
371 372 373 374 375 376
 * @endcode
 * Example (update_manager_install_form_submit()):
 * @code
 *  system_authorized_init('update_authorize_run_install',
 *    drupal_get_path('module', 'update') . '/update.authorize.inc',
 *    $arguments, t('Update manager'));
377
 *  $form_state->setRedirectUrl(system_authorized_get_url());
378
 * @endcode
379 380
 *
 * @param $callback
381
 *   The name of the function to invoke once the user authorizes the operation.
382 383 384 385 386 387 388 389 390
 * @param $file
 *   The full path to the file where the callback function is implemented.
 * @param $arguments
 *   Optional array of arguments to pass into the callback when it is invoked.
 *   Note that the first argument to the callback is always the FileTransfer
 *   object created by authorize.php when the user authorizes the operation.
 * @param $page_title
 *   Optional string to use as the page title once redirected to authorize.php.
 * @return
391
 *   Nothing, this function just initializes variables in the user's session.
392
 */
393
function system_authorized_init($callback, $file, $arguments = array(), $page_title = NULL) {
394 395 396
  // First, figure out what file transfer backends the site supports, and put
  // all of those in the SESSION so that authorize.php has access to all of
  // them via the class autoloader, even without a full bootstrap.
397
  $_SESSION['authorize_filetransfer_info'] = drupal_get_filetransfer_info();
398 399 400 401 402 403 404 405 406

  // Now, define the callback to invoke.
  $_SESSION['authorize_operation'] = array(
    'callback' => $callback,
    'file' => $file,
    'arguments' => $arguments,
  );

  if (isset($page_title)) {
407
    $_SESSION['authorize_page_title'] = $page_title;
408
  }
409
}
410

411 412
/**
 * Return the URL for the authorize.php script.
413 414 415
 *
 * @param array $options
 *   Optional array of options to pass to url().
416
 * @return \Drupal\Core\Url
417
 *   The full URL to authorize.php, using HTTPS if available.
418 419
 *
 * @see system_authorized_init()
420
 */
421
function system_authorized_get_url(array $options = array()) {
422 423
  // core/authorize.php is an unrouted URL, so using the base: scheme is
  // the correct usage for this case.
424
  $url = Url::fromUri('base:core/authorize.php');
425 426 427
  $url_options = $url->getOptions();
  $url->setOptions($options + $url_options);
  return $url;
428 429
}

430 431
/**
 * Returns the URL for the authorize.php script when it is processing a batch.
432 433 434
 *
 * @param array $options
 *   Optional array of options to pass to url().
435 436
 *
 * @return \Drupal\Core\Url
437
 */
438 439 440
function system_authorized_batch_processing_url(array $options = array()) {
  $options['query'] = array('batch' => '1');
  return system_authorized_get_url($options);
441 442
}

443 444 445
/**
 * Setup and invoke an operation using authorize.php.
 *
446
 * @see system_authorized_init()
447 448 449
 */
function system_authorized_run($callback, $file, $arguments = array(), $page_title = NULL) {
  system_authorized_init($callback, $file, $arguments, $page_title);
450
  return new RedirectResponse(system_authorized_get_url()->toString());
451 452
}

453 454 455 456 457 458 459
/**
 * Use authorize.php to run batch_process().
 *
 * @see batch_process()
 */
function system_authorized_batch_process() {
  $finish_url = system_authorized_get_url();
460
  $process_url = system_authorized_batch_processing_url();
461
  return batch_process($finish_url->setAbsolute()->toString(), $process_url);
462 463
}

464 465 466 467
/**
 * @} End of "defgroup authorize".
 */

468
/**
469
 * Implements hook_updater_info().
470 471 472 473
 */
function system_updater_info() {
  return array(
    'module' => array(
474
      'class' => 'Drupal\Core\Updater\Module',
475 476 477 478
      'name' => t('Update modules'),
      'weight' => 0,
    ),
    'theme' => array(
479
      'class' => 'Drupal\Core\Updater\Theme',
480 481 482 483 484 485
      'name' => t('Update themes'),
      'weight' => 0,
    ),
  );
}

486
/**
487
 * Implements hook_filetransfer_info().
488
 */
489
function system_filetransfer_info() {
490 491
  $backends = array();

492
  // This is the default, will be available on most systems.
493
  if (function_exists('ftp_connect')) {
494
    $backends['ftp'] = array(
495
      'title' => t('FTP'),
496
      'class' => 'Drupal\Core\FileTransfer\FTP',
497
      'weight' => 0,
498 499
    );
  }
500

501 502 503 504 505
  // SSH2 lib connection is only available if the proper PHP extension is
  // installed.
  if (function_exists('ssh2_connect')) {
    $backends['ssh'] = array(
      'title' => t('SSH'),
506
      'class' => 'Drupal\Core\FileTransfer\SSH',
507
      'weight' => 20,
508 509 510 511 512
    );
  }
  return $backends;
}

513
/**
514
 * Implements hook_page_attachments().
515 516
 *
 * @see template_preprocess_maintenance_page()
517
 * @see \Drupal\Core\EventSubscriber\ActiveLinkResponseFilter
518
 */
519
function system_page_attachments(array &$page) {
520
  // Ensure the same CSS is loaded in template_preprocess_maintenance_page().
521
  $page['#attached']['library'][] = 'system/base';
522
  if (\Drupal::service('router.admin_context')->isAdminRoute()) {
523
    $page['#attached']['library'][] = 'system/admin';
524
  }
525

526
  // Attach libraries used by this theme.
527 528 529 530 531 532 533 534 535
  $active_theme = \Drupal::theme()->getActiveTheme();
  foreach ($active_theme->getLibraries() as $library) {
    $page['#attached']['library'][] = $library;
  }

  // Attach favicon.
  if (theme_get_setting('features.favicon')) {
    $favicon = theme_get_setting('favicon.url');
    $type = theme_get_setting('favicon.mimetype');
536
    $page['#attached']['html_head_link'][][] = array(
537 538 539 540 541 542 543 544 545 546 547 548 549 550 551 552 553 554 555 556 557 558 559 560 561 562 563 564
      'rel' => 'shortcut icon',
      'href' => UrlHelper::stripDangerousProtocols($favicon),
      'type' => $type,
    );
  }

  // Get the major Drupal version.
  list($version, ) = explode('.', \Drupal::VERSION);

  // Attach default meta tags.
  $meta_default = array(
    // Make sure the Content-Type comes first because the IE browser may be
    // vulnerable to XSS via encoding attacks from any content that comes
    // before this META tag, such as a TITLE tag.
    'system_meta_content_type' => array(
      '#tag' => 'meta',
      '#attributes' => array(
        'charset' => 'utf-8',
      ),
      // Security: This always has to be output first.
      '#weight' => -1000,
    ),
    // Show Drupal and the major version number in the META GENERATOR tag.
    'system_meta_generator' => array(
      '#type' => 'html_tag',
      '#tag' => 'meta',
      '#attributes' => array(
        'name' => 'Generator',
565
        'content' => 'Drupal ' . $version . ' (https://www.drupal.org)',
566 567 568 569 570 571 572 573 574 575 576 577 578 579 580 581 582 583 584 585 586 587 588 589 590 591 592 593 594
      ),
    ),
    // Attach default mobile meta tags for responsive design.
    'MobileOptimized' => array(
      '#tag' => 'meta',
      '#attributes' => array(
        'name' => 'MobileOptimized',
        'content' => 'width',
      ),
    ),
    'HandheldFriendly' => array(
      '#tag' => 'meta',
      '#attributes' => array(
        'name' => 'HandheldFriendly',
        'content' => 'true',
      ),
    ),
    'viewport' => array(
      '#tag' => 'meta',
      '#attributes' => array(
        'name' => 'viewport',
        'content' => 'width=device-width, initial-scale=1.0',
      ),
    ),
  );
  foreach ($meta_default as $key => $value) {
    $page['#attached']['html_head'][] = [$value, $key];
  }

595 596
  // Handle setting the "active" class on links by:
  // - loading the active-link library if the current user is authenticated;
597
  // - applying a response filter if the current user is anonymous.
598 599
  // @see l()
  // @see \Drupal\Core\Utility\LinkGenerator::generate()
600
  // @see template_preprocess_links()
601
  // @see \Drupal\Core\EventSubscriber\ActiveLinkResponseFilter
602
  if (\Drupal::currentUser()->isAuthenticated()) {
603
    $page['#attached']['library'][] = 'core/drupal.active-link';
604
  }
605 606
}

607 608 609 610 611 612 613 614 615 616 617 618 619 620 621 622 623 624 625 626 627 628 629 630 631 632 633 634 635 636 637 638 639 640 641
/**
 * Implements hook_js_settings_build().
 *
 * Sets values for the core/drupal.ajax library, which just depends on the
 * active theme but no other request-dependent values.
 */
function system_js_settings_build(&$settings, AttachedAssetsInterface $assets) {
  // Generate the values for the core/drupal.ajax library.
  // We need to send ajaxPageState settings for core/drupal.ajax if:
  // - ajaxPageState is being loaded in this Response, in which case it will
  //   already exist at $settings['ajaxPageState'] (because the core/drupal.ajax
  //   library definition specifies a placeholder 'ajaxPageState' setting).
  // - core/drupal.ajax already has been loaded and hence this is an AJAX
  //   Response in which we must send the list of extra asset libraries that are
  //   being added in this AJAX Response.
  /** @var \Drupal\Core\Asset\LibraryDependencyResolver $library_dependency_resolver */
  $library_dependency_resolver = \Drupal::service('library.dependency_resolver');
  if (isset($settings['ajaxPageState']) || in_array('core/drupal.ajax', $library_dependency_resolver->getLibrariesWithDependencies($assets->getAlreadyLoadedLibraries()))) {
    // Provide the page with information about the theme that's used, so that
    // a later AJAX request can be rendered using the same theme.
    // @see \Drupal\Core\Theme\AjaxBasePageNegotiator
    $theme_key = \Drupal::theme()->getActiveTheme()->getName();
    $settings['ajaxPageState']['theme'] = $theme_key;

    // Provide the page with information about the individual asset libraries
    // used, information not otherwise available when aggregation is enabled.
    $minimal_libraries = $library_dependency_resolver->getMinimalRepresentativeSubset(array_merge(
      $assets->getLibraries(),
      $assets->getAlreadyLoadedLibraries()
    ));
    sort($minimal_libraries);
    $settings['ajaxPageState']['libraries'] = implode(',', $minimal_libraries);
  }
}

642 643 644
/**
 * Implements hook_js_settings_alter().
 *
645 646
 * Sets values which depend on the current request, like core/drupalSettings
 * as well as theme_token ajax state.
647
 */
648
function system_js_settings_alter(&$settings, AttachedAssetsInterface $assets) {
649 650
  $request = \Drupal::request();
  $current_query = $request->query->all();
651 652 653 654 655 656 657 658

  // Let output path processors set a prefix.
  /** @var \Drupal\Core\PathProcessor\OutboundPathProcessorInterface $path_processor */
  $path_processor = \Drupal::service('path_processor_manager');
  $options = ['prefix' => ''];
  $path_processor->processOutbound('/', $options);
  $pathPrefix = $options['prefix'];

659 660 661 662 663 664 665
  $current_path = \Drupal::routeMatch()->getRouteName() ? Url::fromRouteMatch(\Drupal::routeMatch())->getInternalPath() : '';
  $current_path_is_admin = \Drupal::service('router.admin_context')->isAdminRoute();
  $path_settings = [
    'baseUrl' => $request->getBaseUrl() . '/',
    'pathPrefix' => $pathPrefix,
    'currentPath' => $current_path,
    'currentPathIsAdmin' => $current_path_is_admin,
666
    'isFront' => \Drupal::service('path.matcher')->isFrontPage(),
667 668 669 670 671 672 673 674 675 676 677 678 679
    'currentLanguage' => \Drupal::languageManager()->getCurrentLanguage(LanguageInterface::TYPE_URL)->getId(),
  ];
  if (!empty($current_query)) {
    ksort($current_query);
    $path_settings['currentQuery'] = (object) $current_query;
  }

  // Only set core/drupalSettings values that haven't been set already.
  foreach ($path_settings as $key => $value) {
    if (!isset($settings['path'][$key])) {
      $settings['path'][$key] = $value;
    }
  }
680 681
  if (!isset($settings['pluralDelimiter'])) {
    $settings['pluralDelimiter'] = LOCALE_PLURAL_DELIMITER;
682
  }
683 684
  // Add the theme token to ajaxPageState, ensuring the database is available
  // before doing so.
685 686 687 688
  /** @var \Drupal\Core\Asset\LibraryDependencyResolver $library_dependency_resolver */
  $library_dependency_resolver = \Drupal::service('library.dependency_resolver');
  if (isset($settings['ajaxPageState']) || in_array('core/drupal.ajax', $library_dependency_resolver->getLibrariesWithDependencies($assets->getAlreadyLoadedLibraries()))) {
    if (!defined('MAINTENANCE_MODE')) {
689 690 691 692 693 694 695 696
      // The theme token is only validated when the theme requested is not the
      // default, so don't generate it unless necessary.
      // @see \Drupal\Core\Theme\AjaxBasePageNegotiator::determineActiveTheme()
      $active_theme_key = \Drupal::theme()->getActiveTheme()->getName();
      if ($active_theme_key !== \Drupal::service('theme_handler')->getDefault()) {
        $settings['ajaxPageState']['theme_token'] = \Drupal::csrfToken()
          ->get($active_theme_key);
      }
697 698
    }
  }
699 700
}

701 702 703 704 705 706 707 708 709 710 711 712 713 714 715 716 717 718 719 720 721 722 723
/**
 * Implements hook_form_alter().
 */
function system_form_alter(&$form, FormStateInterface $form_state) {
  // If the page that's being built is cacheable, set the 'immutable' flag, to
  // ensure that when the form is used, a new form build ID is generated when
  // appropriate, to prevent information disclosure.

  // Note: This code just wants to know whether cache response headers are set,
  // not whether page_cache module will be active.
  // \Drupal\Core\EventSubscriber\FinishResponseSubscriber::onRespond will
  // send those headers, in case $request_policy->check($request) succeeds. In
  // that case we need to ensure that the immutable flag is sot, so future POST
  // request won't take over the form state of another user.
  /** @var \Drupal\Core\PageCache\RequestPolicyInterface $request_policy */
  $request_policy = \Drupal::service('page_cache_request_policy');
  $request = \Drupal::requestStack()->getCurrentRequest();
  $request_is_cacheable = $request_policy->check($request) === RequestPolicyInterface::ALLOW;
  if ($request_is_cacheable) {
    $form_state->addBuildInfo('immutable', TRUE);
  }
}

Dries's avatar
 
Dries committed
724
/**
725
 * Implements hook_form_FORM_ID_alter().
Dries's avatar
 
Dries committed
726
 */
727
function system_form_user_form_alter(&$form, FormStateInterface $form_state) {
728
  if (\Drupal::config('system.date')->get('timezone.user.configurable')) {
729
    system_user_timezone($form, $form_state);
730 731 732 733
  }
}

/**
734
 * Implements hook_form_FORM_ID_alter().
735
 */
736
function system_form_user_register_form_alter(&$form, FormStateInterface $form_state) {
737
  $config = \Drupal::config('system.date');
738
  if ($config->get('timezone.user.configurable') && $config->get('timezone.user.default') == DRUPAL_USER_TIMEZONE_SELECT) {
739
    system_user_timezone($form, $form_state);
Dries's avatar
 
Dries committed
740
  }
Dries's avatar
 
Dries committed
741 742
}

743
/**
744
 * Implements hook_ENTITY_TYPE_presave() for user entities.
745
 */
746
function system_user_presave(UserInterface $account) {
747
  $config = \Drupal::config('system.date');
748 749
  if ($config->get('timezone.user.configurable') && !$account->getTimeZone() && !$config->get('timezone.user.default')) {
    $account->timezone = $config->get('timezone.default');
750 751 752
  }
}

753
/**
754
 * Implements hook_user_login().
755
 */
756
function system_user_login(UserInterface $account) {
757
  $config = \Drupal::config('system.date');
758
  // If the user has a NULL time zone, notify them to set a time zone.
759
  if (!$account->getTimezone() && $config->get('timezone.user.configurable') && $config->get('timezone.user.warn')) {
760
    drupal_set_message(t('Configure your <a href=":user-edit">account time zone setting</a>.', array(':user-edit' => $account->url('edit-form', array('query' => \Drupal::destination()->getAsArray(), 'fragment' => 'edit-timezone')))));
761 762 763 764 765 766
  }
}

/**
 * Add the time zone field to the user edit and register forms.
 */
767
function system_user_timezone(&$form, FormStateInterface $form_state) {
768
  $user = \Drupal::currentUser();
769

770
  $account = $form_state->getFormObject()->getEntity();
771
  $form['timezone'] = array(
772
    '#type' => 'details',
773
    '#title' => t('Locale settings'),
774
    '#open' => TRUE,
775 776 777 778 779
    '#weight' => 6,
  );
  $form['timezone']['timezone'] = array(
    '#type' => 'select',
    '#title' => t('Time zone'),
780
    '#default_value' => $account->getTimezone() ? $account->getTimezone() : \Drupal::config('system.date')->get('timezone.default'),
781
    '#options' => system_time_zones($account->id() != $user->id()),
782 783
    '#description' => t('Select the desired local time and time zone. Dates and times throughout this site will be displayed using this time zone.'),
  );
784 785
  $user_input = $form_state->getUserInput();
  if (!$account->getTimezone() && $account->id() == $user->id() && empty($user_input['timezone'])) {
786
    $form['timezone']['#attached']['library'][] = 'core/drupal.timezone';
787
    $form['timezone']['timezone']['#attributes'] = array('class' => array('timezone-detect'));
788 789 790
  }
}

791
/**
792
 * Implements hook_preprocess_HOOK() for block templates.
793
 */
794
function system_preprocess_block(&$variables) {
795
  switch ($variables['base_plugin_id']) {
796 797 798 799 800 801 802 803 804 805 806
    case 'system_branding_block':
      $variables['site_logo'] = '';
      if ($variables['content']['site_logo']['#access'] && $variables['content']['site_logo']['#uri']) {
        $variables['site_logo'] = $variables['content']['site_logo']['#uri'];
      }
      $variables['site_name'] = '';
      if ($variables['content']['site_name']['#access'] && $variables['content']['site_name']['#markup']) {
        $variables['site_name'] = $variables['content']['site_name']['#markup'];
      }
      $variables['site_slogan'] = '';
      if ($variables['content']['site_slogan']['#access'] && $variables['content']['site_slogan']['#markup']) {
807
        $variables['site_slogan']['#markup'] = $variables['content']['site_slogan']['#markup'];
808 809 810
      }
      break;

811
    case 'system_powered_by_block':
812
      $variables['attributes']['role'] = 'complementary';
813
      break;
814
  }
815 816
}

817
/**
818 819 820 821
 * Checks the existence of the directory specified in $form_element.
 *
 * This function is called from the system_settings form to check all core
 * file directories (file_public_path, file_private_path, file_temporary_path).
822 823 824
 *
 * @param $form_element
 *   The form element containing the name of the directory to check.
825 826
 * @param \Drupal\Core\Form\FormStateInterface $form_state
 *   The current state of the form.
827
 */
828
function system_check_directory($form_element, FormStateInterface $form_state) {
829
  $directory = $form_element['#value'];
830 831 832
  if (strlen($directory) == 0) {
    return $form_element;
  }
833

834
  $logger = \Drupal::logger('file system');
835 836
  if (!is_dir($directory) && !drupal_mkdir($directory, NULL, TRUE)) {
    // If the directory does not exists and cannot be created.
837
    $form_state->setErrorByName($form_element['#parents'][0], t('The directory %directory does not exist and could not be created.', array('%directory' => $directory)));
838
    $logger->error('The directory %directory does not exist and could not be created.', array('%directory' => $directory));
839 840 841
  }

  if (is_dir($directory) && !is_writable($directory) && !drupal_chmod($directory)) {
842
    // If the directory is not writable and cannot be made so.
843
    $form_state->setErrorByName($form_element['#parents'][0], t('The directory %directory exists but is not writable and could not be made writable.', array('%directory' => $directory)));
844
    $logger->error('The directory %directory exists but is not writable and could not be made writable.', array('%directory' => $directory));
845
  }
846
  elseif (is_dir($directory)) {
847 848
    if ($form_element['#name'] == 'file_public_path') {
      // Create public .htaccess file.
849
      file_save_htaccess($directory, FALSE);
850 851 852
    }
    else {
      // Create private .htaccess file.
853
      file_save_htaccess($directory);
854 855 856
    }
  }

857 858 859
  return $form_element;
}

860
/**
861
 * Returns an array of information about enabled modules or themes.
862
 *
863
 * This function returns the contents of the .info.yml file for each installed
864
 * module or theme.
865 866 867
 *
 * @param $type
 *   Either 'module' or 'theme'.
868 869 870 871 872
 * @param $name
 *   (optional) The name of a module or theme whose information shall be
 *   returned. If omitted, all records for the provided $type will be returned.
 *   If $name does not exist in the provided $type or is not enabled, an empty
 *   array will be returned.
873
 *
874
 * @return
875 876 877
 *   An associative array of module or theme information keyed by name, or only
 *   information for $name, if given. If no records are available, an empty
 *   array is returned.
878 879
 *
 * @see system_rebuild_module_data()
880
 * @see \Drupal\Core\Extension\ThemeHandlerInterface::rebuildThemeData()
881
 */
882 883
function system_get_info($type, $name = NULL) {
  if ($type == 'module') {
884 885 886 887 888 889 890 891 892 893 894 895 896 897 898 899 900
    $info = &drupal_static(__FUNCTION__);
    if (!isset($info)) {
      if ($cache = \Drupal::cache()->get('system.module.info')) {
        $info = $cache->data;
      }
      else {
        $data = system_rebuild_module_data();
        foreach (\Drupal::moduleHandler()->getModuleList() as $module => $filename) {
          if (isset($data[$module])) {
            $info[$module] = $data[$module]->info;
          }
        }
        // Store the module information in cache. This cache is cleared by
        // calling system_rebuild_module_data(), for example, when listing
        // modules, (un)installing modules, importing configuration, updating
        // the site and when flushing all the caches.
        \Drupal::cache()->set('system.module.info', $info);
901
      }
902
    }
903
  }
904
  else {
905
    $info = array();
906 907 908 909 910
    $list = system_list($type);
    foreach ($list as $shortname => $item) {
      if (!empty($item->status)) {
        $info[$shortname] = $item->info;
      }
911 912 913 914
    }
  }
  if (isset($name)) {
    return isset($info[$name]) ? $info[$name] : array();
915 916 917 918
  }
  return $info;
}

Dries's avatar
 
Dries committed
919
/**
920
 * Helper function to scan and collect module .info.yml data.
921
 *
922
 * @return \Drupal\Core\Extension\Extension[]
923
 *   An associative array of module information.
Dries's avatar
 
Dries committed
924
 */
925
function _system_rebuild_module_data() {
926
  $listing = new ExtensionDiscovery(\Drupal::root());
927

928 929
  // Find installation profiles. This needs to happen before performing a
  // module scan as the module scan requires knowing what the active profile is.
930
  // @todo Remove as part of https://www.drupal.org/node/2186491.
931
  $profiles = $listing->scan('profile');
932 933 934 935 936
  $profile = drupal_get_profile();
  if ($profile && isset($profiles[$profile])) {
    // Prime the drupal_get_filename() static cache with the profile info file
    // location so we can use drupal_get_path() on the active profile during
    // the module scan.
937
    // @todo Remove as part of https://www.drupal.org/node/2186491.
938 939
    drupal_get_filename('profile', $profile, $profiles[$profile]->getPathname());
  }
940

941 942
  // Find modules.
  $modules = $listing->scan('module');
943
  // Include the installation profile in modules that are loaded.
944
  if ($profile) {
945 946 947 948
    $modules[$profile] = $profiles[$profile];
    // Installation profile hooks are always executed last.
    $modules[$profile]->weight = 1000;
  }
949

950 951 952 953
  // Set defaults for module info.
  $defaults = array(
    'dependencies' => array(),
    'description' => '',
954
    'package' => 'Other',
955 956 957
    'version' => NULL,
    'php' => DRUPAL_MINIMUM_PHP,
  );
Dries's avatar
 
Dries committed
958

959 960 961
  // Read info files for each module.
  foreach ($modules as $key => $module) {
    // Look for the info file.
962
    $module->info = \Drupal::service('info_parser')->parse($module->getPathname());
963

964 965
    // Add the info file modification time, so it becomes available for
    // contributed modules to use for ordering module lists.
966
    $module->info['mtime'] = $module->getMTime();
967

968 969 970
    // Merge in defaults and save.
    $modules[$key]->info = $module->info + $defaults;

971
    // Installation profiles are hidden by default, unless explicitly specified
972
    // otherwise in the .info.yml file.
973 974 975 976
    if ($key == $profile && !isset($modules[$key]->info['hidden'])) {
      $modules[$key]->info['hidden'] = TRUE;
    }

977
    // Invoke hook_system_info_alter() to give installed modules a chance to
978
    // modify the data in the .info.yml files if necessary.
979
    // @todo Remove $type argument, obsolete with $module->getType().
980
    $type = 'module';
981
    \Drupal::moduleHandler()->alter('system_info', $modules[$key]->info, $modules[$key], $type);
982
  }
983

984 985 986 987 988 989 990
  // It is possible that a module was marked as required by
  // hook_system_info_alter() and modules that it depends on are not required.
  foreach ($modules as $module) {
    _system_rebuild_module_data_ensure_required($module, $modules);
  }


991
  if ($profile && isset($modules[$profile])) {
jhodgdon's avatar