Initial Drupal 11 with DDEV setup
This commit is contained in:
564
web/core/includes/batch.inc
Normal file
564
web/core/includes/batch.inc
Normal file
@ -0,0 +1,564 @@
|
||||
<?php
|
||||
|
||||
/**
|
||||
* @file
|
||||
* Batch processing API for processes to run in multiple HTTP requests.
|
||||
*
|
||||
* Note that batches are usually invoked by form submissions, which is
|
||||
* why the core interaction functions of the batch processing API live in
|
||||
* form.inc.
|
||||
*
|
||||
* @see form.inc
|
||||
* @see batch_set()
|
||||
* @see batch_process()
|
||||
* @see batch_get()
|
||||
*/
|
||||
|
||||
use Drupal\Component\Utility\Timer;
|
||||
use Drupal\Component\Utility\UrlHelper;
|
||||
use Drupal\Core\Batch\Percentage;
|
||||
use Drupal\Core\Form\FormState;
|
||||
use Drupal\Core\Url;
|
||||
use Symfony\Component\HttpFoundation\JsonResponse;
|
||||
use Symfony\Component\HttpFoundation\Request;
|
||||
use Symfony\Component\HttpFoundation\RedirectResponse;
|
||||
use Symfony\Component\HttpKernel\Exception\NotFoundHttpException;
|
||||
|
||||
/**
|
||||
* Renders the batch processing page based on the current state of the batch.
|
||||
*
|
||||
* @param \Symfony\Component\HttpFoundation\Request $request
|
||||
* The current request object.
|
||||
*
|
||||
* @see _batch_shutdown()
|
||||
*/
|
||||
function _batch_page(Request $request) {
|
||||
$batch = &batch_get();
|
||||
|
||||
if (!($request_id = $request->query->get('id'))) {
|
||||
return FALSE;
|
||||
}
|
||||
|
||||
// Retrieve the current state of the batch.
|
||||
if (!$batch) {
|
||||
$batch = \Drupal::service('batch.storage')->load($request_id);
|
||||
if (!$batch) {
|
||||
\Drupal::messenger()->addError(t('No batch with ID @batch exists.', ['@batch' => $request_id]));
|
||||
throw new NotFoundHttpException(sprintf('No batch with ID %s exists.', $request_id));
|
||||
}
|
||||
}
|
||||
|
||||
// We need to store the updated batch information in the batch storage after
|
||||
// processing the batch. In order for the error page to work correctly this
|
||||
// needs to be done even in case of a PHP fatal error in which case the end of
|
||||
// this function is never reached. Therefore we register a shutdown function
|
||||
// to handle this case. Because with FastCGI and fastcgi_finish_request()
|
||||
// shutdown functions are called after the HTTP connection is closed, updating
|
||||
// the batch information in a shutdown function would lead to race conditions
|
||||
// between consecutive requests if the batch processing continues. In case of
|
||||
// a fatal error the processing stops anyway, so it works even with FastCGI.
|
||||
// However, we must ensure to only update in the shutdown phase in this
|
||||
// particular case we track whether the batch information still needs to be
|
||||
// updated.
|
||||
// @see _batch_shutdown()
|
||||
// @see \Symfony\Component\HttpFoundation\Response::send()
|
||||
drupal_register_shutdown_function('_batch_shutdown');
|
||||
_batch_needs_update(TRUE);
|
||||
|
||||
$build = [];
|
||||
|
||||
// Add batch-specific libraries.
|
||||
foreach ($batch['sets'] as $batch_set) {
|
||||
if (isset($batch_set['library'])) {
|
||||
foreach ($batch_set['library'] as $library) {
|
||||
$build['#attached']['library'][] = $library;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
$response = FALSE;
|
||||
$op = $request->query->get('op', '');
|
||||
switch ($op) {
|
||||
case 'start':
|
||||
case 'do_nojs':
|
||||
// Display the full progress page on startup and on each additional
|
||||
// non-JavaScript iteration.
|
||||
$current_set = _batch_current_set();
|
||||
$build['#title'] = $current_set['title'];
|
||||
$build['content'] = _batch_progress_page();
|
||||
|
||||
$response = $build;
|
||||
break;
|
||||
|
||||
case 'do':
|
||||
// JavaScript-based progress page callback.
|
||||
$response = _batch_do();
|
||||
break;
|
||||
|
||||
case 'finished':
|
||||
// _batch_finished() returns a RedirectResponse.
|
||||
$response = _batch_finished();
|
||||
break;
|
||||
}
|
||||
|
||||
if ($batch) {
|
||||
\Drupal::service('batch.storage')->update($batch);
|
||||
}
|
||||
_batch_needs_update(FALSE);
|
||||
|
||||
return $response;
|
||||
}
|
||||
|
||||
/**
|
||||
* Checks whether the batch information needs to be updated in the storage.
|
||||
*
|
||||
* @param bool $new_value
|
||||
* (optional) A new value to set.
|
||||
*
|
||||
* @return bool
|
||||
* TRUE if the batch information needs to be updated; FALSE otherwise.
|
||||
*/
|
||||
function _batch_needs_update($new_value = NULL) {
|
||||
$needs_update = &drupal_static(__FUNCTION__, FALSE);
|
||||
|
||||
if (isset($new_value)) {
|
||||
$needs_update = $new_value;
|
||||
}
|
||||
|
||||
return $needs_update;
|
||||
}
|
||||
|
||||
/**
|
||||
* Does one execution pass with JavaScript and returns progress to the browser.
|
||||
*
|
||||
* @see _batch_progress_page_js()
|
||||
* @see _batch_process()
|
||||
*/
|
||||
function _batch_do() {
|
||||
// Perform actual processing.
|
||||
[$percentage, $message, $label] = _batch_process();
|
||||
|
||||
return new JsonResponse(['status' => TRUE, 'percentage' => $percentage, 'message' => $message, 'label' => $label]);
|
||||
}
|
||||
|
||||
/**
|
||||
* Outputs a batch processing page.
|
||||
*
|
||||
* @see _batch_process()
|
||||
*/
|
||||
function _batch_progress_page(): array {
|
||||
$batch = &batch_get();
|
||||
|
||||
$current_set = _batch_current_set();
|
||||
|
||||
$new_op = 'do_nojs';
|
||||
|
||||
if (!isset($batch['running'])) {
|
||||
// This is the first page so we return some output immediately.
|
||||
$percentage = 0;
|
||||
$message = $current_set['init_message'];
|
||||
$label = '';
|
||||
$batch['running'] = TRUE;
|
||||
}
|
||||
else {
|
||||
// This is one of the later requests; do some processing first.
|
||||
|
||||
// Error handling: if PHP dies due to a fatal error (e.g. a nonexistent
|
||||
// function), it will output whatever is in the output buffer, followed by
|
||||
// the error message.
|
||||
ob_start();
|
||||
$fallback = $current_set['error_message'] . '<br />' . $batch['error_message'];
|
||||
|
||||
// We strip the end of the page using a marker in the template, so any
|
||||
// additional HTML output by PHP shows up inside the page rather than below
|
||||
// it. While this causes invalid HTML, the same would be true if we didn't,
|
||||
// as content is not allowed to appear after </html> anyway.
|
||||
$bare_html_page_renderer = \Drupal::service('bare_html_page_renderer');
|
||||
$response = $bare_html_page_renderer->renderBarePage(['#markup' => $fallback], $current_set['title'], 'maintenance_page', [
|
||||
'#show_messages' => FALSE,
|
||||
]);
|
||||
|
||||
// Just use the content of the response.
|
||||
$fallback = $response->getContent();
|
||||
|
||||
[$fallback] = explode('<!--partial-->', $fallback);
|
||||
print $fallback;
|
||||
|
||||
// Perform actual processing.
|
||||
[$percentage, $message, $label] = _batch_process();
|
||||
if ($percentage == 100) {
|
||||
$new_op = 'finished';
|
||||
}
|
||||
|
||||
// PHP did not die; remove the fallback output.
|
||||
ob_end_clean();
|
||||
}
|
||||
|
||||
// Merge required query parameters for batch processing into those provided by
|
||||
// batch_set() or hook_batch_alter().
|
||||
$query_options = $batch['url']->getOption('query');
|
||||
$query_options['id'] = $batch['id'];
|
||||
$query_options['op'] = $new_op;
|
||||
$batch['url']->setOption('query', $query_options);
|
||||
|
||||
$url = $batch['url']->toString(TRUE)->getGeneratedUrl();
|
||||
|
||||
$build = [
|
||||
'#theme' => 'progress_bar',
|
||||
'#percent' => $percentage,
|
||||
'#message' => ['#markup' => $message],
|
||||
'#label' => $label,
|
||||
'#attached' => [
|
||||
'html_head' => [
|
||||
[
|
||||
[
|
||||
// Redirect through a 'Refresh' meta tag if JavaScript is disabled.
|
||||
'#tag' => 'meta',
|
||||
'#noscript' => TRUE,
|
||||
'#attributes' => [
|
||||
'http-equiv' => 'Refresh',
|
||||
'content' => '0; URL=' . $url,
|
||||
],
|
||||
],
|
||||
'batch_progress_meta_refresh',
|
||||
],
|
||||
],
|
||||
// Adds JavaScript code and settings for clients where JavaScript is
|
||||
// enabled.
|
||||
'drupalSettings' => [
|
||||
'batch' => [
|
||||
'errorMessage' => $current_set['error_message'] . '<br />' . $batch['error_message'],
|
||||
'initMessage' => $current_set['init_message'],
|
||||
'uri' => $url,
|
||||
],
|
||||
],
|
||||
'library' => [
|
||||
'core/drupal.batch',
|
||||
],
|
||||
],
|
||||
];
|
||||
return $build;
|
||||
}
|
||||
|
||||
/**
|
||||
* Processes sets in a batch.
|
||||
*
|
||||
* If the batch was marked for progressive execution (default), this executes as
|
||||
* many operations in batch sets until an execution time of 1 second has been
|
||||
* exceeded. It will continue with the next operation of the same batch set in
|
||||
* the next request.
|
||||
*
|
||||
* @return array
|
||||
* An array containing a completion value (in percent) and a status message.
|
||||
*/
|
||||
function _batch_process() {
|
||||
$batch = &batch_get();
|
||||
$current_set = &_batch_current_set();
|
||||
// Indicate that this batch set needs to be initialized.
|
||||
$set_changed = TRUE;
|
||||
$task_message = '';
|
||||
|
||||
// If this batch was marked for progressive execution (e.g. forms submitted by
|
||||
// \Drupal::formBuilder()->submitForm(), initialize a timer to determine
|
||||
// whether we need to proceed with the same batch phase when a processing time
|
||||
// of 1 second has been exceeded.
|
||||
if ($batch['progressive']) {
|
||||
Timer::start('batch_processing');
|
||||
}
|
||||
|
||||
if (empty($current_set['start'])) {
|
||||
$current_set['start'] = microtime(TRUE);
|
||||
}
|
||||
|
||||
$queue = _batch_queue($current_set);
|
||||
|
||||
while (!$current_set['success']) {
|
||||
// If this is the first time we iterate this batch set in the current
|
||||
// request, we check if it requires an additional file for functions
|
||||
// definitions.
|
||||
if ($set_changed && isset($current_set['file']) && is_file($current_set['file'])) {
|
||||
include_once \Drupal::root() . '/' . $current_set['file'];
|
||||
}
|
||||
|
||||
$task_message = '';
|
||||
// Assume a single pass operation and set the completion level to 1 by
|
||||
// default.
|
||||
$finished = 1;
|
||||
|
||||
if ($item = $queue->claimItem()) {
|
||||
[$callback, $args] = $item->data;
|
||||
|
||||
// Build the 'context' array and execute the function call.
|
||||
$batch_context = [
|
||||
'sandbox' => &$current_set['sandbox'],
|
||||
'results' => &$current_set['results'],
|
||||
'finished' => &$finished,
|
||||
'message' => &$task_message,
|
||||
];
|
||||
call_user_func_array($callback, array_merge($args, [&$batch_context]));
|
||||
|
||||
if ($finished >= 1) {
|
||||
// Make sure this step is not counted twice when computing $current.
|
||||
$finished = 0;
|
||||
// Remove the processed operation and clear the sandbox.
|
||||
$queue->deleteItem($item);
|
||||
$current_set['count']--;
|
||||
$current_set['sandbox'] = [];
|
||||
}
|
||||
}
|
||||
|
||||
// When all operations in the current batch set are completed, browse
|
||||
// through the remaining sets, marking them 'successfully processed'
|
||||
// along the way, until we find a set that contains operations.
|
||||
// _batch_next_set() executes form submit handlers stored in 'control'
|
||||
// sets (see \Drupal::service('form_submitter')), which can in turn add new
|
||||
// sets to the batch.
|
||||
$set_changed = FALSE;
|
||||
$old_set = $current_set;
|
||||
while (empty($current_set['count']) && ($current_set['success'] = TRUE) && _batch_next_set()) {
|
||||
$current_set = &_batch_current_set();
|
||||
$current_set['start'] = microtime(TRUE);
|
||||
$set_changed = TRUE;
|
||||
}
|
||||
|
||||
// At this point, either $current_set contains operations that need to be
|
||||
// processed or all sets have been completed.
|
||||
$queue = _batch_queue($current_set);
|
||||
|
||||
// If we are in progressive mode, break processing after 1 second.
|
||||
if ($batch['progressive'] && Timer::read('batch_processing') > 1000) {
|
||||
// Record elapsed wall clock time.
|
||||
$current_set['elapsed'] = round((microtime(TRUE) - $current_set['start']) * 1000, 2);
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if ($batch['progressive']) {
|
||||
// Gather progress information.
|
||||
|
||||
// Reporting 100% progress will cause the whole batch to be considered
|
||||
// processed. If processing was paused right after moving to a new set,
|
||||
// we have to use the info from the new (unprocessed) set.
|
||||
if ($set_changed && isset($current_set['queue'])) {
|
||||
// Processing will continue with a fresh batch set.
|
||||
$remaining = $current_set['count'];
|
||||
$total = $current_set['total'];
|
||||
$progress_message = $current_set['init_message'];
|
||||
$task_message = '';
|
||||
}
|
||||
else {
|
||||
// Processing will continue with the current batch set.
|
||||
$remaining = $old_set['count'] ?? 0;
|
||||
$total = $old_set['total'] ?? 0;
|
||||
$progress_message = $old_set['progress_message'] ?? '';
|
||||
}
|
||||
|
||||
// Total progress is the number of operations that have fully run plus the
|
||||
// completion level of the current operation.
|
||||
$current = $total - $remaining + ($finished ?? 0);
|
||||
$percentage = _batch_api_percentage($total, $current);
|
||||
$elapsed = $current_set['elapsed'] ?? 0;
|
||||
$values = [
|
||||
'@remaining' => $remaining,
|
||||
'@total' => $total,
|
||||
'@current' => floor($current),
|
||||
'@percentage' => $percentage,
|
||||
'@elapsed' => \Drupal::service('date.formatter')->formatInterval((int) ($elapsed / 1000)),
|
||||
// If possible, estimate remaining processing time.
|
||||
'@estimate' => ($current > 0) ? \Drupal::service('date.formatter')->formatInterval((int) (($elapsed * ($total - $current) / $current) / 1000)) : '-',
|
||||
];
|
||||
$message = strtr($progress_message, $values);
|
||||
|
||||
return [$percentage, $message, $task_message];
|
||||
}
|
||||
else {
|
||||
// If we are not in progressive mode, the entire batch has been processed.
|
||||
return _batch_finished();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Formats the percent completion for a batch set.
|
||||
*
|
||||
* @param int $total
|
||||
* The total number of operations.
|
||||
* @param int|float $current
|
||||
* The number of the current operation. This may be a floating point number
|
||||
* rather than an integer in the case of a multi-step operation that is not
|
||||
* yet complete; in that case, the fractional part of $current represents the
|
||||
* fraction of the operation that has been completed.
|
||||
*
|
||||
* @return string
|
||||
* The properly formatted percentage, as a string. We output percentages
|
||||
* using the correct number of decimal places so that we never print "100%"
|
||||
* until we are finished, but we also never print more decimal places than
|
||||
* are meaningful.
|
||||
*
|
||||
* @see _batch_process()
|
||||
*/
|
||||
function _batch_api_percentage($total, $current) {
|
||||
return Percentage::format($total, $current);
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the batch set being currently processed.
|
||||
*/
|
||||
function &_batch_current_set() {
|
||||
$batch = &batch_get();
|
||||
return $batch['sets'][$batch['current_set']];
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieves the next set in a batch.
|
||||
*
|
||||
* If there is a subsequent set in this batch, assign it as the new set to
|
||||
* process and execute its form submit handler (if defined), which may add
|
||||
* further sets to this batch.
|
||||
*
|
||||
* @return true|null
|
||||
* TRUE if a subsequent set was found in the batch; no value will be returned
|
||||
* if no subsequent set was found.
|
||||
*/
|
||||
function _batch_next_set() {
|
||||
$batch = &batch_get();
|
||||
$set_indexes = array_keys($batch['sets']);
|
||||
$current_set_index_key = array_search($batch['current_set'], $set_indexes);
|
||||
if (isset($set_indexes[$current_set_index_key + 1])) {
|
||||
$batch['current_set'] = $set_indexes[$current_set_index_key + 1];
|
||||
$current_set = &_batch_current_set();
|
||||
if (isset($current_set['form_submit']) && ($callback = $current_set['form_submit']) && is_callable($callback)) {
|
||||
// We use our stored copies of $form and $form_state to account for
|
||||
// possible alterations by previous form submit handlers.
|
||||
$complete_form = &$batch['form_state']->getCompleteForm();
|
||||
call_user_func_array($callback, [&$complete_form, &$batch['form_state']]);
|
||||
}
|
||||
return TRUE;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Ends the batch processing.
|
||||
*
|
||||
* Call the 'finished' callback of each batch set to allow custom handling of
|
||||
* the results and resolve page redirection.
|
||||
*/
|
||||
function _batch_finished() {
|
||||
$batch = &batch_get();
|
||||
$batch_finished_redirect = NULL;
|
||||
|
||||
// Execute the 'finished' callbacks for each batch set, if defined.
|
||||
foreach ($batch['sets'] as $batch_set) {
|
||||
if (isset($batch_set['finished'])) {
|
||||
// Check if the set requires an additional file for function definitions.
|
||||
if (isset($batch_set['file']) && is_file($batch_set['file'])) {
|
||||
include_once \Drupal::root() . '/' . $batch_set['file'];
|
||||
}
|
||||
if (is_callable($batch_set['finished'])) {
|
||||
$queue = _batch_queue($batch_set);
|
||||
$operations = $queue->getAllItems();
|
||||
$batch_set_result = call_user_func_array($batch_set['finished'], [$batch_set['success'], $batch_set['results'], $operations, \Drupal::service('date.formatter')->formatInterval((int) ($batch_set['elapsed'] / 1000))]);
|
||||
// If a batch 'finished' callback requested a redirect after the batch
|
||||
// is complete, save that for later use. If more than one batch set
|
||||
// returned a redirect, the last one is used.
|
||||
if ($batch_set_result instanceof RedirectResponse) {
|
||||
$batch_finished_redirect = $batch_set_result;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Clean up the batch table and unset the static $batch variable.
|
||||
if ($batch['progressive']) {
|
||||
\Drupal::service('batch.storage')->delete($batch['id']);
|
||||
foreach ($batch['sets'] as $batch_set) {
|
||||
if ($queue = _batch_queue($batch_set)) {
|
||||
$queue->deleteQueue();
|
||||
}
|
||||
}
|
||||
// Clean-up the session. Not needed for CLI updates.
|
||||
$session = \Drupal::request()->getSession();
|
||||
$batches = $session->get('batches', []);
|
||||
unset($batches[$batch['id']]);
|
||||
if (empty($batches)) {
|
||||
$session->remove('batches');
|
||||
}
|
||||
else {
|
||||
$session->set('batches', $batches);
|
||||
}
|
||||
}
|
||||
$_batch = $batch;
|
||||
$batch = NULL;
|
||||
|
||||
// Redirect if needed.
|
||||
if ($_batch['progressive']) {
|
||||
// Revert the 'destination' that was saved in batch_process().
|
||||
if (isset($_batch['destination'])) {
|
||||
\Drupal::request()->query->set('destination', $_batch['destination']);
|
||||
}
|
||||
|
||||
// Determine the target path to redirect to. If a batch 'finished' callback
|
||||
// returned a redirect response object, use that. Otherwise, fall back on
|
||||
// the form redirection.
|
||||
if (isset($batch_finished_redirect)) {
|
||||
return $batch_finished_redirect;
|
||||
}
|
||||
elseif (!isset($_batch['form_state'])) {
|
||||
$_batch['form_state'] = new FormState();
|
||||
}
|
||||
if ($_batch['form_state']->getRedirect() === NULL) {
|
||||
$redirect = $_batch['batch_redirect'] ?: $_batch['source_url'];
|
||||
// Any path with a scheme does not correspond to a route.
|
||||
if (!$redirect instanceof Url) {
|
||||
$options = UrlHelper::parse($redirect);
|
||||
if (parse_url($options['path'], PHP_URL_SCHEME)) {
|
||||
$redirect = Url::fromUri($options['path'], $options);
|
||||
}
|
||||
else {
|
||||
$redirect = \Drupal::pathValidator()->getUrlIfValid($options['path']);
|
||||
if (!$redirect) {
|
||||
// Stay on the same page if the redirect was invalid.
|
||||
$redirect = Url::fromRoute('<current>');
|
||||
}
|
||||
$redirect->setOptions($options);
|
||||
}
|
||||
}
|
||||
$_batch['form_state']->setRedirectUrl($redirect);
|
||||
}
|
||||
|
||||
// Use \Drupal\Core\Form\FormSubmitterInterface::redirectForm() to handle
|
||||
// the redirection logic.
|
||||
$redirect = \Drupal::service('form_submitter')->redirectForm($_batch['form_state']);
|
||||
if (is_object($redirect)) {
|
||||
return $redirect;
|
||||
}
|
||||
|
||||
// If no redirection happened, redirect to the originating page. In case the
|
||||
// form needs to be rebuilt, save the final $form_state for
|
||||
// \Drupal\Core\Form\FormBuilderInterface::buildForm().
|
||||
if ($_batch['form_state']->isRebuilding()) {
|
||||
$session = \Drupal::request()->getSession();
|
||||
$session->set('batch_form_state', $_batch['form_state']);
|
||||
}
|
||||
$callback = $_batch['redirect_callback'];
|
||||
$_batch['source_url']->mergeOptions(['query' => ['op' => 'finish', 'id' => $_batch['id']]]);
|
||||
if (is_callable($callback)) {
|
||||
$callback($_batch['source_url'], $_batch['source_url']->getOption('query'));
|
||||
}
|
||||
elseif ($callback === NULL) {
|
||||
// Default to RedirectResponse objects when nothing specified.
|
||||
return new RedirectResponse($_batch['source_url']->setAbsolute()->toString());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Shutdown function: Stores the current batch data for the next request.
|
||||
*
|
||||
* @see _batch_page()
|
||||
* @see drupal_register_shutdown_function()
|
||||
*/
|
||||
function _batch_shutdown(): void {
|
||||
if (($batch = batch_get()) && _batch_needs_update()) {
|
||||
\Drupal::service('batch.storage')->update($batch);
|
||||
}
|
||||
}
|
||||
511
web/core/includes/bootstrap.inc
Normal file
511
web/core/includes/bootstrap.inc
Normal file
@ -0,0 +1,511 @@
|
||||
<?php
|
||||
|
||||
/**
|
||||
* @file
|
||||
* Functions that need to be loaded on every Drupal request.
|
||||
*/
|
||||
|
||||
use Drupal\Component\Utility\Crypt;
|
||||
use Drupal\Core\Test\TestDatabase;
|
||||
use Drupal\Core\Utility\Error;
|
||||
use Drupal\Core\StringTranslation\TranslatableMarkup;
|
||||
|
||||
// cspell:ignore htkey
|
||||
|
||||
/**
|
||||
* Error reporting level: display no errors.
|
||||
*/
|
||||
const ERROR_REPORTING_HIDE = 'hide';
|
||||
|
||||
/**
|
||||
* Error reporting level: display errors and warnings.
|
||||
*/
|
||||
const ERROR_REPORTING_DISPLAY_SOME = 'some';
|
||||
|
||||
/**
|
||||
* Error reporting level: display all messages.
|
||||
*/
|
||||
const ERROR_REPORTING_DISPLAY_ALL = 'all';
|
||||
|
||||
/**
|
||||
* Error reporting level: display all messages, plus backtrace information.
|
||||
*/
|
||||
const ERROR_REPORTING_DISPLAY_VERBOSE = 'verbose';
|
||||
|
||||
/**
|
||||
* The maximum number of characters in a module or theme name.
|
||||
*/
|
||||
const DRUPAL_EXTENSION_NAME_MAX_LENGTH = 50;
|
||||
|
||||
/**
|
||||
* Defines the root directory of the Drupal installation.
|
||||
*
|
||||
* This strips two levels of directories off the current directory.
|
||||
*/
|
||||
define('DRUPAL_ROOT', dirname(__DIR__, 2));
|
||||
|
||||
/**
|
||||
* Translates a string to the current language or to a given language.
|
||||
*
|
||||
* In order for strings to be localized, make them available in one of the ways
|
||||
* supported by the @link i18n Localization API. @endlink When possible, use
|
||||
* the \Drupal\Core\StringTranslation\StringTranslationTrait $this->t().
|
||||
* Otherwise create a new \Drupal\Core\StringTranslation\TranslatableMarkup
|
||||
* object directly.
|
||||
*
|
||||
* See \Drupal\Core\StringTranslation\TranslatableMarkup::__construct() for
|
||||
* important security information and usage guidelines.
|
||||
*
|
||||
* @param string $string
|
||||
* A string containing the English text to translate.
|
||||
* @param array $args
|
||||
* (optional) An associative array of replacements to make after translation.
|
||||
* Based on the first character of the key, the value is escaped and/or
|
||||
* themed. See
|
||||
* \Drupal\Component\Render\FormattableMarkup::placeholderFormat() for
|
||||
* details.
|
||||
* @param array $options
|
||||
* (optional) An associative array of additional options, with the following
|
||||
* elements:
|
||||
* - 'langcode' (defaults to the current language): A language code, to
|
||||
* translate to a language other than what is used to display the page.
|
||||
* - 'context' (defaults to the empty context): The context the source string
|
||||
* belongs to. See the @link i18n Internationalization topic @endlink for
|
||||
* more information about string contexts.
|
||||
*
|
||||
* @return \Drupal\Core\StringTranslation\TranslatableMarkup
|
||||
* An object that, when cast to a string, returns the translated string.
|
||||
*
|
||||
* @see \Drupal\Component\Render\FormattableMarkup::placeholderFormat()
|
||||
* @see \Drupal\Core\StringTranslation\StringTranslationTrait::t()
|
||||
* @see \Drupal\Core\StringTranslation\TranslatableMarkup::__construct()
|
||||
*
|
||||
* @ingroup sanitization
|
||||
*/
|
||||
function t($string, array $args = [], array $options = []) {
|
||||
// phpcs:ignore Drupal.Semantics.FunctionT.NotLiteralString
|
||||
return new TranslatableMarkup($string, $args, $options);
|
||||
}
|
||||
|
||||
/**
|
||||
* Provides custom PHP error handling.
|
||||
*
|
||||
* @param int $error_level
|
||||
* The level of the error raised.
|
||||
* @param string $message
|
||||
* The error message.
|
||||
* @param string $filename
|
||||
* (optional) The filename that the error was raised in.
|
||||
* @param int $line
|
||||
* (optional) The line number the error was raised at.
|
||||
*/
|
||||
function _drupal_error_handler($error_level, $message, $filename = NULL, $line = NULL): void {
|
||||
require_once __DIR__ . '/errors.inc';
|
||||
_drupal_error_handler_real($error_level, $message, $filename, $line);
|
||||
}
|
||||
|
||||
/**
|
||||
* Provides custom PHP exception handling.
|
||||
*
|
||||
* Uncaught exceptions are those not enclosed in a try/catch block. They are
|
||||
* always fatal: the execution of the script will stop as soon as the exception
|
||||
* handler exits.
|
||||
*
|
||||
* @param \Exception|\Throwable $exception
|
||||
* The exception object that was thrown.
|
||||
*/
|
||||
function _drupal_exception_handler($exception): void {
|
||||
require_once __DIR__ . '/errors.inc';
|
||||
|
||||
try {
|
||||
// Log the message to the watchdog and return an error page to the user.
|
||||
_drupal_log_error(Error::decodeException($exception), TRUE);
|
||||
}
|
||||
// Catch \Throwable, which covers both Error and Exception throwables.
|
||||
catch (\Throwable $error) {
|
||||
_drupal_exception_handler_additional($exception, $error);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Displays any additional errors caught while handling an exception.
|
||||
*
|
||||
* @param \Exception|\Throwable $exception
|
||||
* The first exception object that was thrown.
|
||||
* @param \Exception|\Throwable $exception2
|
||||
* The second exception object that was thrown.
|
||||
*/
|
||||
function _drupal_exception_handler_additional($exception, $exception2): void {
|
||||
// Another uncaught exception was thrown while handling the first one.
|
||||
// If we are displaying errors, then do so with no possibility of a further
|
||||
// uncaught exception being thrown.
|
||||
if (error_displayable()) {
|
||||
print '<h1>Additional uncaught exception thrown while handling exception.</h1>';
|
||||
print '<h2>Original</h2><p>' . Error::renderExceptionSafe($exception) . '</p>';
|
||||
print '<h2>Additional</h2><p>' . Error::renderExceptionSafe($exception2) . '</p><hr />';
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the test prefix if this is an internal request from a test.
|
||||
*
|
||||
* @param string $new_prefix
|
||||
* Internal use only. A new prefix to be stored.
|
||||
*
|
||||
* @return string|false
|
||||
* Either the simpletest prefix (the string "simpletest" followed by any
|
||||
* number of digits) or FALSE if the user agent does not contain a valid
|
||||
* HMAC and timestamp.
|
||||
*/
|
||||
function drupal_valid_test_ua($new_prefix = NULL) {
|
||||
static $test_prefix;
|
||||
|
||||
if (isset($new_prefix)) {
|
||||
$test_prefix = $new_prefix;
|
||||
}
|
||||
if (isset($test_prefix)) {
|
||||
return $test_prefix;
|
||||
}
|
||||
// Unless the below User-Agent and HMAC validation succeeds, we are not in
|
||||
// a test environment.
|
||||
$test_prefix = FALSE;
|
||||
|
||||
// A valid test request will contain a hashed and salted authentication code.
|
||||
// Check if this code is present in a cookie or custom user agent string.
|
||||
$http_user_agent = $_SERVER['HTTP_USER_AGENT'] ?? NULL;
|
||||
$user_agent = $_COOKIE['SIMPLETEST_USER_AGENT'] ?? $http_user_agent;
|
||||
if (isset($user_agent) && preg_match("/^simple(\w+\d+):(.+):(.+):(.+)$/", $user_agent, $matches)) {
|
||||
[, $prefix, $time, $salt, $hmac] = $matches;
|
||||
$check_string = $prefix . ':' . $time . ':' . $salt;
|
||||
// Read the hash salt prepared by drupal_generate_test_ua().
|
||||
// This function is called before settings.php is read and Drupal's error
|
||||
// handlers are set up. While Drupal's error handling may be properly
|
||||
// configured on production sites, the server's PHP error_reporting may not.
|
||||
// Ensure that no information leaks on production sites.
|
||||
$test_db = new TestDatabase($prefix);
|
||||
$key_file = DRUPAL_ROOT . '/' . $test_db->getTestSitePath() . '/.htkey';
|
||||
if (!is_readable($key_file) || is_dir($key_file)) {
|
||||
header($_SERVER['SERVER_PROTOCOL'] . ' 403 Forbidden');
|
||||
exit;
|
||||
}
|
||||
$private_key = file_get_contents($key_file);
|
||||
// The string from drupal_generate_test_ua() is 74 bytes long. If we don't
|
||||
// have it, tests cannot be allowed.
|
||||
if (empty($private_key) || strlen($private_key) < 74) {
|
||||
header($_SERVER['SERVER_PROTOCOL'] . ' 403 Forbidden');
|
||||
exit;
|
||||
}
|
||||
// The file properties add more entropy not easily accessible to others.
|
||||
$key = $private_key . filectime(__FILE__) . fileinode(__FILE__);
|
||||
$time_diff = time() - $time;
|
||||
$test_hmac = Crypt::hmacBase64($check_string, $key);
|
||||
// Since we are making a local request a 600 second time window is allowed,
|
||||
// and the HMAC must match.
|
||||
if ($time_diff >= 0 && $time_diff <= 600 && hash_equals($test_hmac, $hmac)) {
|
||||
$test_prefix = $prefix;
|
||||
}
|
||||
else {
|
||||
header($_SERVER['SERVER_PROTOCOL'] . ' 403 Forbidden (SIMPLETEST_USER_AGENT invalid)');
|
||||
exit;
|
||||
}
|
||||
}
|
||||
return $test_prefix;
|
||||
}
|
||||
|
||||
/**
|
||||
* Generates a user agent string with a HMAC and timestamp for tests.
|
||||
*
|
||||
* @return string|null
|
||||
* The user agent, or NULL on failure.
|
||||
*/
|
||||
function drupal_generate_test_ua($prefix) {
|
||||
static $key, $last_prefix;
|
||||
|
||||
if (!isset($key) || $last_prefix != $prefix) {
|
||||
$last_prefix = $prefix;
|
||||
$test_db = new TestDatabase($prefix);
|
||||
$key_file = DRUPAL_ROOT . '/' . $test_db->getTestSitePath() . '/.htkey';
|
||||
// When issuing an outbound HTTP client request from within an inbound test
|
||||
// request, then the outbound request has to use the same User-Agent header
|
||||
// as the inbound request. A newly generated private key for the same test
|
||||
// prefix would invalidate all subsequent inbound requests.
|
||||
// @see \Drupal\Core\Test\HttpClientMiddleware\TestHttpClientMiddleware
|
||||
if (DRUPAL_TEST_IN_CHILD_SITE && $parent_prefix = drupal_valid_test_ua()) {
|
||||
if ($parent_prefix != $prefix) {
|
||||
throw new \RuntimeException("Malformed User-Agent: Expected '$parent_prefix' but got '$prefix'.");
|
||||
}
|
||||
// If the file is not readable, a PHP warning is expected in this case.
|
||||
$private_key = file_get_contents($key_file);
|
||||
}
|
||||
else {
|
||||
// Generate and save a new hash salt for a test run.
|
||||
// Consumed by drupal_valid_test_ua() before settings.php is loaded.
|
||||
$private_key = Crypt::randomBytesBase64(55);
|
||||
if (!@file_put_contents($key_file, $private_key)) {
|
||||
return NULL;
|
||||
}
|
||||
}
|
||||
// The file properties add more entropy not easily accessible to others.
|
||||
$key = $private_key . filectime(__FILE__) . fileinode(__FILE__);
|
||||
}
|
||||
// Generate a moderately secure HMAC based on the database credentials.
|
||||
$salt = uniqid('', TRUE);
|
||||
$check_string = $prefix . ':' . time() . ':' . $salt;
|
||||
return 'simple' . $check_string . ':' . Crypt::hmacBase64($check_string, $key);
|
||||
}
|
||||
|
||||
/**
|
||||
* Enables use of the theme system without requiring database access.
|
||||
*
|
||||
* Loads and initializes the theme system for site installs, updates and when
|
||||
* the site is in maintenance mode. This also applies when the database fails.
|
||||
*
|
||||
* @see _drupal_maintenance_theme()
|
||||
*/
|
||||
function drupal_maintenance_theme(): void {
|
||||
require_once __DIR__ . '/theme.maintenance.inc';
|
||||
_drupal_maintenance_theme();
|
||||
}
|
||||
|
||||
/**
|
||||
* Provides central static variable storage.
|
||||
*
|
||||
* All functions requiring a static variable to persist or cache data within
|
||||
* a single page request are encouraged to use this function unless it is
|
||||
* absolutely certain that the static variable will not need to be reset during
|
||||
* the page request. By centralizing static variable storage through this
|
||||
* function, other functions can rely on a consistent API for resetting any
|
||||
* other function's static variables.
|
||||
*
|
||||
* Example:
|
||||
* @code
|
||||
* function example_list($field = 'default') {
|
||||
* $examples = &drupal_static(__FUNCTION__);
|
||||
* if (!isset($examples)) {
|
||||
* // If this function is being called for the first time after a reset,
|
||||
* // query the database and execute any other code needed to retrieve
|
||||
* // information.
|
||||
* ...
|
||||
* }
|
||||
* if (!isset($examples[$field])) {
|
||||
* // If this function is being called for the first time for a particular
|
||||
* // index field, then execute code needed to index the information already
|
||||
* // available in $examples by the desired field.
|
||||
* ...
|
||||
* }
|
||||
* // Subsequent invocations of this function for a particular index field
|
||||
* // skip the above two code blocks and quickly return the already indexed
|
||||
* // information.
|
||||
* return $examples[$field];
|
||||
* }
|
||||
* function examples_admin_overview() {
|
||||
* // When building the content for the overview page, make sure to get
|
||||
* // completely fresh information.
|
||||
* drupal_static_reset('example_list');
|
||||
* ...
|
||||
* }
|
||||
* @endcode
|
||||
*
|
||||
* In a few cases, a function can have certainty that there is no legitimate
|
||||
* use-case for resetting that function's static variable. This is rare,
|
||||
* because when writing a function, it's hard to forecast all the situations in
|
||||
* which it will be used. A guideline is that if a function's static variable
|
||||
* does not depend on any information outside of the function that might change
|
||||
* during a single page request, then it's ok to use the "static" keyword
|
||||
* instead of the drupal_static() function.
|
||||
*
|
||||
* Example:
|
||||
* @code
|
||||
* function my_module_log_stream_handle($new_handle = NULL) {
|
||||
* static $handle;
|
||||
* if (isset($new_handle)) {
|
||||
* $handle = $new_handle;
|
||||
* }
|
||||
* return $handle;
|
||||
* }
|
||||
* @endcode
|
||||
*
|
||||
* In a few cases, a function needs a resettable static variable, but the
|
||||
* function is called many times (100+) during a single page request, so
|
||||
* every microsecond of execution time that can be removed from the function
|
||||
* counts. These functions can use a more cumbersome, but faster variant of
|
||||
* calling drupal_static(). It works by storing the reference returned by
|
||||
* drupal_static() in the calling function's own static variable, thereby
|
||||
* removing the need to call drupal_static() for each iteration of the function.
|
||||
* Conceptually, it replaces:
|
||||
* @code
|
||||
* $foo = &drupal_static(__FUNCTION__);
|
||||
* @endcode
|
||||
* with:
|
||||
* @code
|
||||
* // Unfortunately, this does not work.
|
||||
* static $foo = &drupal_static(__FUNCTION__);
|
||||
* @endcode
|
||||
* However, the above line of code does not work, because PHP only allows static
|
||||
* variables to be initialized by literal values, and does not allow static
|
||||
* variables to be assigned to references.
|
||||
* - http://php.net/manual/language.variables.scope.php#language.variables.scope.static
|
||||
* - http://php.net/manual/language.variables.scope.php#language.variables.scope.references
|
||||
* The example below shows the syntax needed to work around both limitations.
|
||||
* For benchmarks and more information, see https://www.drupal.org/node/619666.
|
||||
*
|
||||
* Example:
|
||||
* @code
|
||||
* function example_default_format_type() {
|
||||
* // Use the advanced drupal_static() pattern, since this is called very often.
|
||||
* static $drupal_static_fast;
|
||||
* if (!isset($drupal_static_fast)) {
|
||||
* $drupal_static_fast['format_type'] = &drupal_static(__FUNCTION__);
|
||||
* }
|
||||
* $format_type = &$drupal_static_fast['format_type'];
|
||||
* ...
|
||||
* }
|
||||
* @endcode
|
||||
*
|
||||
* @param string|null $name
|
||||
* Globally unique name for the variable. For a function with only one static,
|
||||
* variable, the function name (e.g. via the PHP magic __FUNCTION__ constant)
|
||||
* is recommended. For a function with multiple static variables add a
|
||||
* distinguishing suffix to the function name for each one.
|
||||
* @param mixed|null $default_value
|
||||
* Optional default value.
|
||||
* @param bool $reset
|
||||
* TRUE to reset one or all variables(s). This parameter is only used
|
||||
* internally and should not be passed in; use drupal_static_reset() instead.
|
||||
* (This function's return value should not be used when TRUE is passed in.)
|
||||
*
|
||||
* @return mixed
|
||||
* Returns a variable by reference.
|
||||
*
|
||||
* @see drupal_static_reset()
|
||||
*/
|
||||
function &drupal_static($name, $default_value = NULL, $reset = FALSE) {
|
||||
static $data = [], $defaults = [];
|
||||
if (isset($name)) {
|
||||
// Check if we're dealing with a previously defined static variable.
|
||||
if (\array_key_exists($name, $data)) {
|
||||
// Both $data[$name] and (implicitly) $defaults[$name] statics exist.
|
||||
if ($reset) {
|
||||
// Reset pre-existing static variable to its default value.
|
||||
$data[$name] = $defaults[$name];
|
||||
}
|
||||
}
|
||||
else {
|
||||
// Neither $data[$name] nor $defaults[$name] static variables exist.
|
||||
if ($reset) {
|
||||
// Reset was called before any value for $name was set, so we should
|
||||
// not set anything ($default_value is not reliable in this case). As
|
||||
// the function returns a reference, we must still return a variable.
|
||||
// (Code using $reset does not use the return value).
|
||||
return $data;
|
||||
}
|
||||
// First call with new non-NULL $name. Initialize a new static variable.
|
||||
$defaults[$name] = $data[$name] = $default_value;
|
||||
}
|
||||
// Return a reference to the named variable.
|
||||
return $data[$name];
|
||||
}
|
||||
else {
|
||||
// Reset all: ($name == NULL). This needs to be done one at a time so that
|
||||
// references returned by earlier invocations of drupal_static() also get
|
||||
// reset.
|
||||
foreach ($defaults as $name => $value) {
|
||||
$data[$name] = $value;
|
||||
}
|
||||
// As the function returns a reference, we must still return a variable.
|
||||
return $data;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Resets one or all centrally stored static variable(s).
|
||||
*
|
||||
* @param string $name
|
||||
* Name of the static variable to reset. Omit to reset all variables.
|
||||
* Resetting all variables should only be used, for example, for running
|
||||
* unit tests with a clean environment.
|
||||
*/
|
||||
function drupal_static_reset($name = NULL): void {
|
||||
drupal_static($name, NULL, TRUE);
|
||||
}
|
||||
|
||||
/**
|
||||
* Registers a function for execution on shutdown.
|
||||
*
|
||||
* Wrapper for register_shutdown_function() that catches thrown exceptions to
|
||||
* avoid "Exception thrown without a stack frame in Unknown".
|
||||
*
|
||||
* @param callable $callback
|
||||
* The shutdown function to register.
|
||||
* @param mixed ...$args
|
||||
* Additional arguments to pass to the shutdown function.
|
||||
*
|
||||
* @return array
|
||||
* Array of shutdown functions to be executed.
|
||||
*
|
||||
* @see register_shutdown_function()
|
||||
* @ingroup php_wrappers
|
||||
*/
|
||||
function &drupal_register_shutdown_function($callback = NULL, ...$args) {
|
||||
// We cannot use drupal_static() here because the static cache is reset during
|
||||
// batch processing, which breaks batch handling.
|
||||
static $callbacks = [];
|
||||
|
||||
if (isset($callback)) {
|
||||
// Only register the internal shutdown function once.
|
||||
if (empty($callbacks)) {
|
||||
register_shutdown_function('_drupal_shutdown_function');
|
||||
}
|
||||
// Save callback and arguments
|
||||
$callbacks[] = ['callback' => $callback, 'arguments' => $args];
|
||||
}
|
||||
return $callbacks;
|
||||
}
|
||||
|
||||
/**
|
||||
* Executes registered shutdown functions.
|
||||
*/
|
||||
function _drupal_shutdown_function(): void {
|
||||
$callbacks = &drupal_register_shutdown_function();
|
||||
|
||||
// Set the CWD to DRUPAL_ROOT as it is not guaranteed to be the same as it
|
||||
// was in the normal context of execution.
|
||||
chdir(DRUPAL_ROOT);
|
||||
|
||||
try {
|
||||
reset($callbacks);
|
||||
// Do not use foreach() here because it is possible that the callback will
|
||||
// add to the $callbacks array via drupal_register_shutdown_function().
|
||||
while ($callback = current($callbacks)) {
|
||||
call_user_func_array($callback['callback'], $callback['arguments']);
|
||||
next($callbacks);
|
||||
}
|
||||
}
|
||||
// Catch \Throwable, which covers both Error and Exception throwables.
|
||||
catch (\Throwable $error) {
|
||||
_drupal_shutdown_function_handle_exception($error);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Displays and logs any errors that may happen during shutdown.
|
||||
*
|
||||
* @param \Exception|\Throwable $exception
|
||||
* The exception object that was thrown.
|
||||
*
|
||||
* @see _drupal_shutdown_function()
|
||||
*/
|
||||
function _drupal_shutdown_function_handle_exception($exception): void {
|
||||
// If using PHP-FPM then fastcgi_finish_request() will have been fired
|
||||
// preventing further output to the browser.
|
||||
if (!function_exists('fastcgi_finish_request')) {
|
||||
// If we are displaying errors, then do so with no possibility of a
|
||||
// further uncaught exception being thrown.
|
||||
require_once __DIR__ . '/errors.inc';
|
||||
if (error_displayable()) {
|
||||
print '<h1>Uncaught exception thrown in shutdown function.</h1>';
|
||||
print '<p>' . Error::renderExceptionSafe($exception) . '</p><hr />';
|
||||
}
|
||||
}
|
||||
error_log($exception);
|
||||
}
|
||||
530
web/core/includes/common.inc
Normal file
530
web/core/includes/common.inc
Normal file
@ -0,0 +1,530 @@
|
||||
<?php
|
||||
|
||||
/**
|
||||
* @file
|
||||
* Common functions that many Drupal modules will need to reference.
|
||||
*
|
||||
* The functions that are critical and need to be available even when serving
|
||||
* a cached page are instead located in bootstrap.inc.
|
||||
*/
|
||||
|
||||
use Drupal\Component\Utility\SortArray;
|
||||
use Drupal\Core\Cache\Cache;
|
||||
use Drupal\Core\Cache\CacheTagsPurgeInterface;
|
||||
use Drupal\Core\DrupalKernel;
|
||||
|
||||
/**
|
||||
* @defgroup php_wrappers PHP wrapper functions
|
||||
* @{
|
||||
* Functions that are wrappers or custom implementations of PHP functions.
|
||||
*
|
||||
* Certain PHP functions should not be used in Drupal. Instead, Drupal's
|
||||
* replacement functions should be used.
|
||||
*
|
||||
* For example, for improved or more secure UTF8-handling, or RFC-compliant
|
||||
* handling of URLs in Drupal.
|
||||
*
|
||||
* For ease of use and memorizing, all these wrapper functions use the same name
|
||||
* as the original PHP function, but prefixed with "drupal_". Beware, however,
|
||||
* that not all wrapper functions support the same arguments as the original
|
||||
* functions.
|
||||
*
|
||||
* You should always use these wrapper functions in your code.
|
||||
*
|
||||
* Wrong:
|
||||
* @code
|
||||
* $my_substring = substr($original_string, 0, 5);
|
||||
* @endcode
|
||||
*
|
||||
* Correct:
|
||||
* @code
|
||||
* $my_substring = mb_substr($original_string, 0, 5);
|
||||
* @endcode
|
||||
*
|
||||
* @}
|
||||
*/
|
||||
|
||||
/**
|
||||
* Return status for saving which involved creating a new item.
|
||||
*/
|
||||
const SAVED_NEW = 1;
|
||||
|
||||
/**
|
||||
* Return status for saving which involved an update to an existing item.
|
||||
*/
|
||||
const SAVED_UPDATED = 2;
|
||||
|
||||
/**
|
||||
* Return status for saving which deleted an existing item.
|
||||
*
|
||||
* @deprecated in drupal:11.2.0 and is removed from drupal:12.0.0.
|
||||
* There is no replacement.
|
||||
*
|
||||
* @see https://www.drupal.org/node/3328750
|
||||
*/
|
||||
const SAVED_DELETED = 3;
|
||||
|
||||
/**
|
||||
* The default aggregation group for CSS files added to the page.
|
||||
*/
|
||||
const CSS_AGGREGATE_DEFAULT = 0;
|
||||
|
||||
/**
|
||||
* The default aggregation group for theme CSS files added to the page.
|
||||
*/
|
||||
const CSS_AGGREGATE_THEME = 100;
|
||||
|
||||
/**
|
||||
* The default weight for CSS rules that style HTML elements ("base" styles).
|
||||
*/
|
||||
const CSS_BASE = -200;
|
||||
|
||||
/**
|
||||
* The default weight for CSS rules that layout a page.
|
||||
*/
|
||||
const CSS_LAYOUT = -100;
|
||||
|
||||
/**
|
||||
* The default weight for CSS rules that style design components.
|
||||
*
|
||||
* This applies to the associated states and themes of the component.
|
||||
*/
|
||||
const CSS_COMPONENT = 0;
|
||||
|
||||
/**
|
||||
* The default weight for rules that style states not included with components.
|
||||
*/
|
||||
const CSS_STATE = 100;
|
||||
|
||||
/**
|
||||
* The default weight for rules that style themes not included with components.
|
||||
*/
|
||||
const CSS_THEME = 200;
|
||||
|
||||
/**
|
||||
* The default group for JavaScript settings added to the page.
|
||||
*/
|
||||
const JS_SETTING = -200;
|
||||
|
||||
/**
|
||||
* The default group for JavaScript and jQuery libraries added to the page.
|
||||
*/
|
||||
const JS_LIBRARY = -100;
|
||||
|
||||
/**
|
||||
* The default group for module JavaScript code added to the page.
|
||||
*/
|
||||
const JS_DEFAULT = 0;
|
||||
|
||||
/**
|
||||
* The default group for theme JavaScript code added to the page.
|
||||
*/
|
||||
const JS_THEME = 100;
|
||||
|
||||
/**
|
||||
* Returns the base URL path (i.e., directory) of the Drupal installation.
|
||||
*
|
||||
* Function base_path() adds a "/" to the beginning and end of the returned path
|
||||
* if the path is not empty. At the very least, this will return "/".
|
||||
*
|
||||
* Examples:
|
||||
* - http://example.com returns "/" because the path is empty.
|
||||
* - http://example.com/drupal/folder returns "/drupal/folder/".
|
||||
*/
|
||||
function base_path() {
|
||||
return $GLOBALS['base_path'];
|
||||
}
|
||||
|
||||
/**
|
||||
* Assists in attaching the tableDrag JavaScript behavior to a themed table.
|
||||
*
|
||||
* Draggable tables should be used wherever an outline or list of sortable items
|
||||
* needs to be arranged by an end-user. Draggable tables are very flexible and
|
||||
* can manipulate the value of form elements placed within individual columns.
|
||||
*
|
||||
* To set up a table to use drag and drop in place of weight select-lists or in
|
||||
* place of a form that contains parent relationships, the form must be themed
|
||||
* into a table. The table must have an ID attribute set and it
|
||||
* may be set as follows:
|
||||
* @code
|
||||
* $table = [
|
||||
* '#type' => 'table',
|
||||
* '#header' => $header,
|
||||
* '#rows' => $rows,
|
||||
* '#attributes' => [
|
||||
* 'id' => 'my-module-table',
|
||||
* ],
|
||||
* ];
|
||||
* return \Drupal::service('renderer')->render($table);
|
||||
* @endcode
|
||||
*
|
||||
* In the theme function for the form, a special class must be added to each
|
||||
* form element within the same column, "grouping" them together.
|
||||
*
|
||||
* In a situation where a single weight column is being sorted in the table, the
|
||||
* classes could be added like this (in the theme function):
|
||||
* @code
|
||||
* $form['my_elements'][$delta]['weight']['#attributes']['class'] = ['my-elements-weight'];
|
||||
* @endcode
|
||||
*
|
||||
* Each row of the table must also have a class of "draggable" in order to
|
||||
* enable the drag handles:
|
||||
* @code
|
||||
* $row = [...];
|
||||
* $rows[] = [
|
||||
* 'data' => $row,
|
||||
* 'class' => ['draggable'],
|
||||
* ];
|
||||
* @endcode
|
||||
*
|
||||
* When tree relationships are present, the two additional classes
|
||||
* 'tabledrag-leaf' and 'tabledrag-root' can be used to refine the behavior:
|
||||
* - Rows with the 'tabledrag-leaf' class cannot have child rows.
|
||||
* - Rows with the 'tabledrag-root' class cannot be nested under a parent row.
|
||||
*
|
||||
* Calling drupal_attach_tabledrag() would then be written as such:
|
||||
* @code
|
||||
* drupal_attach_tabledrag('my-module-table', [
|
||||
* 'action' => 'order',
|
||||
* 'relationship' => 'sibling',
|
||||
* 'group' => 'my-elements-weight',
|
||||
* ];
|
||||
* @endcode
|
||||
*
|
||||
* In a more complex case where there are several groups in one column (such as
|
||||
* the block regions on the admin/structure/block page), a separate subgroup
|
||||
* class must also be added to differentiate the groups.
|
||||
* @code
|
||||
* $form['my_elements'][$region][$delta]['weight']['#attributes']['class'] = ['my-elements-weight', 'my-elements-weight-' . $region];
|
||||
* @endcode
|
||||
*
|
||||
* The 'group' option is still 'my-element-weight', and the additional
|
||||
* 'subgroup' option will be passed in as 'my-elements-weight-' . $region. This
|
||||
* also means that you'll need to call drupal_attach_tabledrag() once for every
|
||||
* region added.
|
||||
*
|
||||
* @code
|
||||
* foreach ($regions as $region) {
|
||||
* drupal_attach_tabledrag('my-module-table', [
|
||||
* 'action' => 'order',
|
||||
* 'relationship' => 'sibling',
|
||||
* 'group' => 'my-elements-weight',
|
||||
* 'subgroup' => 'my-elements-weight-' . $region,
|
||||
* ]);
|
||||
* }
|
||||
* @endcode
|
||||
*
|
||||
* In a situation where tree relationships are present, adding multiple
|
||||
* subgroups is not necessary, because the table will contain indentations that
|
||||
* provide enough information about the sibling and parent relationships. See
|
||||
* MenuForm::BuildOverviewForm for an example creating a table
|
||||
* containing parent relationships.
|
||||
*
|
||||
* @param array $element
|
||||
* A form element to attach the tableDrag behavior to.
|
||||
* @param array $options
|
||||
* These options are used to generate JavaScript settings necessary to
|
||||
* configure the tableDrag behavior appropriately for this particular table.
|
||||
* An associative array containing the following keys:
|
||||
* - 'table_id': String containing the target table's id attribute.
|
||||
* If the table does not have an id, one will need to be set,
|
||||
* such as <table id="my-module-table">.
|
||||
* - 'action': String describing the action to be done on the form item.
|
||||
* Either 'match' 'depth', or 'order':
|
||||
* - 'match' is typically used for parent relationships.
|
||||
* - 'order' is typically used to set weights on other form elements with
|
||||
* the same group.
|
||||
* - 'depth' updates the target element with the current indentation.
|
||||
* - 'relationship': String describing where the "action" option
|
||||
* should be performed. Either 'parent', 'sibling', 'group', or 'self':
|
||||
* - 'parent' will only look for fields up the tree.
|
||||
* - 'sibling' will look for fields in the same group in rows above and
|
||||
* below it.
|
||||
* - 'self' affects the dragged row itself.
|
||||
* - 'group' affects the dragged row, plus any children below it (the entire
|
||||
* dragged group).
|
||||
* - 'group': A class name applied on all related form elements for this
|
||||
* action.
|
||||
* - 'subgroup': (optional) If the group has several subgroups within it, this
|
||||
* string should contain the class name identifying fields in the same
|
||||
* subgroup.
|
||||
* - 'source': (optional) If the $action is 'match', this string should
|
||||
* contain the classname identifying what field will be used as the source
|
||||
* value when matching the value in $subgroup.
|
||||
* - 'hidden': (optional) The column containing the field elements may be
|
||||
* entirely hidden from view dynamically when the JavaScript is loaded. Set
|
||||
* to FALSE if the column should not be hidden.
|
||||
* - 'limit': (optional) Limit the maximum amount of parenting in this table.
|
||||
*
|
||||
* @see MenuForm::BuildOverviewForm()
|
||||
*/
|
||||
function drupal_attach_tabledrag(&$element, array $options): void {
|
||||
// Add default values to elements.
|
||||
$options = $options + [
|
||||
'subgroup' => NULL,
|
||||
'source' => NULL,
|
||||
'hidden' => TRUE,
|
||||
'limit' => 0,
|
||||
];
|
||||
|
||||
$group = $options['group'];
|
||||
|
||||
$tabledrag_id = &drupal_static(__FUNCTION__);
|
||||
$tabledrag_id = (!isset($tabledrag_id)) ? 0 : $tabledrag_id + 1;
|
||||
|
||||
// If a subgroup or source isn't set, assume it is the same as the group.
|
||||
$target = $options['subgroup'] ?? $group;
|
||||
$source = $options['source'] ?? $target;
|
||||
$element['#attached']['drupalSettings']['tableDrag'][$options['table_id']][$group][$tabledrag_id] = [
|
||||
'target' => $target,
|
||||
'source' => $source,
|
||||
'relationship' => $options['relationship'],
|
||||
'action' => $options['action'],
|
||||
'hidden' => $options['hidden'],
|
||||
'limit' => $options['limit'],
|
||||
];
|
||||
|
||||
$element['#attached']['library'][] = 'core/drupal.tabledrag';
|
||||
}
|
||||
|
||||
/**
|
||||
* Hides an element from later rendering.
|
||||
*
|
||||
* The first time render() or RenderInterface::render() is called on an element
|
||||
* tree, as each element in the tree is rendered, it is marked with a #printed
|
||||
* flag and the rendered children of the element are cached. Subsequent calls to
|
||||
* render() or RenderInterface::render() will not traverse the child tree of
|
||||
* this element again: they will just use the cached children. So if you want to
|
||||
* hide an element, be sure to call hide() on the element before its parent tree
|
||||
* is rendered for the first time, as it will have no effect on subsequent
|
||||
* renderings of the parent tree.
|
||||
*
|
||||
* @param array $element
|
||||
* The element to be hidden.
|
||||
*
|
||||
* @return array
|
||||
* The element.
|
||||
*
|
||||
* @see \Drupal\Core\Render\RendererInterface
|
||||
* @see render()
|
||||
* @see show()
|
||||
*/
|
||||
function hide(&$element) {
|
||||
$element['#printed'] = TRUE;
|
||||
return $element;
|
||||
}
|
||||
|
||||
/**
|
||||
* Shows a hidden element for later rendering.
|
||||
*
|
||||
* You can also use render($element), which shows the element while rendering
|
||||
* it.
|
||||
*
|
||||
* The first time render() or RenderInterface::render() is called on an element
|
||||
* tree, as each element in the tree is rendered, it is marked with a #printed
|
||||
* flag and the rendered children of the element are cached. Subsequent calls to
|
||||
* render() or RenderInterface::render() will not traverse the child tree of
|
||||
* this element again: they will just use the cached children. So if you want to
|
||||
* show an element, be sure to call show() on the element before its parent tree
|
||||
* is rendered for the first time, as it will have no effect on subsequent
|
||||
* renderings of the parent tree.
|
||||
*
|
||||
* @param array $element
|
||||
* The element to be shown.
|
||||
*
|
||||
* @return array
|
||||
* The element.
|
||||
*
|
||||
* @see \Drupal\Core\Render\RendererInterface
|
||||
* @see render()
|
||||
* @see hide()
|
||||
*/
|
||||
function show(&$element) {
|
||||
$element['#printed'] = FALSE;
|
||||
return $element;
|
||||
}
|
||||
|
||||
/**
|
||||
* Re-initializes the entire system.
|
||||
*
|
||||
* To re-initialize the system, the container is rebuilt, all persistent caches
|
||||
* are flushed, all variables are reset and all data structures are rebuilt.
|
||||
*
|
||||
* At times, it is necessary to re-initialize the entire system to account for
|
||||
* changed or new code. This function:
|
||||
* - Rebuilds the container if $kernel is not passed in.
|
||||
* - Clears all persistent caches:
|
||||
* - The bootstrap cache bin containing base system, module system, and theme
|
||||
* system information.
|
||||
* - The common 'default' cache bin containing arbitrary caches.
|
||||
* - The page cache.
|
||||
* - The URL alias path cache.
|
||||
* - Resets all static variables that have been defined via drupal_static().
|
||||
* - Clears asset (JS/CSS) file caches.
|
||||
* - Updates the system with latest information about extensions (modules and
|
||||
* themes).
|
||||
* - Updates the bootstrap flag for modules implementing bootstrap_hooks().
|
||||
* - Rebuilds the full database schema information (invoking hook_schema()).
|
||||
* - Rebuilds data structures of all modules (invoking hook_rebuild()). In
|
||||
* core this means
|
||||
* - blocks, node types, date formats and actions are synchronized with the
|
||||
* database
|
||||
* - The 'active' status of fields is refreshed.
|
||||
* - Rebuilds the menu router.
|
||||
*
|
||||
* It's discouraged to call this during a regular page request.
|
||||
* If you call this function in tests, every code afterwards should use the new
|
||||
* container.
|
||||
*
|
||||
* This means the entire system is reset so all caches and static variables are
|
||||
* effectively empty. After that is guaranteed, information about the currently
|
||||
* active code is updated, and rebuild operations are successively called in
|
||||
* order to synchronize the active system according to the current information
|
||||
* defined in code.
|
||||
*
|
||||
* All modules need to ensure that all of their caches are flushed when
|
||||
* hook_cache_flush() is invoked; any previously known information must no
|
||||
* longer exist. All following hook_rebuild() operations must be based on fresh
|
||||
* and current system data. All modules must be able to rely on this contract.
|
||||
*
|
||||
* This function also resets the theme, which means it is not initialized
|
||||
* anymore and all previously added JavaScript and CSS is gone. Normally, this
|
||||
* function is called as an end-of-POST-request operation that is followed by a
|
||||
* redirect, so this effect is not visible. Since the full reset is the whole
|
||||
* point of this function, callers need to take care for backing up all needed
|
||||
* variables and properly restoring or re-initializing them on their own. For
|
||||
* convenience, this function automatically re-initializes the maintenance theme
|
||||
* if it was initialized before.
|
||||
*
|
||||
* @param \Drupal\Core\DrupalKernel|array $kernel
|
||||
* (optional) The Drupal Kernel. It is the caller's responsibility to rebuild
|
||||
* the container if this is passed in. Sometimes drupal_flush_all_caches is
|
||||
* used as a batch operation so $kernel will be an array, in this instance it
|
||||
* will be treated as if it is NULL.
|
||||
*
|
||||
* @see \Drupal\Core\Cache\CacheHelper::getBins()
|
||||
* @see hook_cache_flush()
|
||||
* @see hook_rebuild()
|
||||
*
|
||||
* @todo Try to clear page/JS/CSS caches last, so cached pages can still be
|
||||
* served during this possibly long-running operation. (Conflict on bootstrap
|
||||
* cache though.)
|
||||
* @todo Add a global lock to ensure that caches are not primed in concurrent
|
||||
* requests.
|
||||
*/
|
||||
function drupal_flush_all_caches($kernel = NULL): void {
|
||||
// This is executed based on old/previously known information if $kernel is
|
||||
// not passed in, which is sufficient, since new extensions cannot have any
|
||||
// primed caches yet.
|
||||
$module_handler = \Drupal::moduleHandler();
|
||||
// Flush all persistent caches.
|
||||
$module_handler->invokeAll('cache_flush');
|
||||
// Purge cache tags immediately before flushing cache bins. If a cache tag is
|
||||
// invalidated between the tags being purged and cache bins are flushed, then
|
||||
// it will be included in the checksum of any new cache items, but still valid
|
||||
// because the tag was written before the creation of cache item.
|
||||
if (($invalidator = \Drupal::service('cache_tags.invalidator')) && ($invalidator instanceof CacheTagsPurgeInterface)) {
|
||||
$invalidator->purge();
|
||||
}
|
||||
foreach (Cache::getBins() as $cache_backend) {
|
||||
$cache_backend->deleteAll();
|
||||
}
|
||||
|
||||
// Flush asset file caches.
|
||||
\Drupal::service('asset.css.collection_optimizer')->deleteAll();
|
||||
\Drupal::service('asset.js.collection_optimizer')->deleteAll();
|
||||
\Drupal::service('asset.query_string')->reset();
|
||||
|
||||
// Reset all static caches.
|
||||
drupal_static_reset();
|
||||
|
||||
// Wipe the Twig PHP Storage cache.
|
||||
\Drupal::service('twig')->invalidate();
|
||||
|
||||
// Rebuild profile, profile, theme_engine and theme data.
|
||||
\Drupal::service('extension.list.profile')->reset();
|
||||
\Drupal::service('extension.list.theme_engine')->reset();
|
||||
\Drupal::service('theme_handler')->refreshInfo();
|
||||
// In case the active theme gets requested later in the same request we need
|
||||
// to reset the theme manager.
|
||||
\Drupal::theme()->resetActiveTheme();
|
||||
|
||||
if (!$kernel instanceof DrupalKernel) {
|
||||
$kernel = \Drupal::service('kernel');
|
||||
$kernel->invalidateContainer();
|
||||
$kernel->rebuildContainer();
|
||||
}
|
||||
|
||||
// Rebuild module data that is stored in state.
|
||||
\Drupal::service('extension.list.module')->reset();
|
||||
|
||||
// Reload modules.
|
||||
\Drupal::moduleHandler()->reload();
|
||||
|
||||
// Rebuild all information based on new module data.
|
||||
\Drupal::moduleHandler()->invokeAll('rebuild');
|
||||
|
||||
// Clear all plugin caches.
|
||||
\Drupal::service('plugin.cache_clearer')->clearCachedDefinitions();
|
||||
|
||||
// Rebuild the menu router based on all rebuilt data.
|
||||
// Important: This rebuild must happen last, so the menu router is guaranteed
|
||||
// to be based on up to date information.
|
||||
\Drupal::service('router.builder')->rebuild();
|
||||
|
||||
// Re-initialize the maintenance theme, if the current request attempted to
|
||||
// use it. Unlike regular usages of this function, the installer and update
|
||||
// scripts need to flush all caches during GET requests/page building.
|
||||
if (function_exists('_drupal_maintenance_theme')) {
|
||||
\Drupal::theme()->resetActiveTheme();
|
||||
drupal_maintenance_theme();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Assembles the Drupal Updater registry.
|
||||
*
|
||||
* An Updater is a class that knows how to update various parts of the Drupal
|
||||
* file system, for example to update modules that have newer releases, or to
|
||||
* install a new theme.
|
||||
*
|
||||
* @return array
|
||||
* The Drupal Updater class registry.
|
||||
*
|
||||
* @see \Drupal\Core\Updater\Updater
|
||||
* @see hook_updater_info()
|
||||
* @see hook_updater_info_alter()
|
||||
*/
|
||||
function drupal_get_updaters() {
|
||||
@trigger_error(__FUNCTION__ . '() is deprecated in drupal:11.2.0 and is removed from drupal:12.0.0. There is no replacement. Use composer to manage the code for your site. See https://www.drupal.org/node/3512364', E_USER_DEPRECATED);
|
||||
|
||||
$updaters = &drupal_static(__FUNCTION__);
|
||||
if (!isset($updaters)) {
|
||||
$updaters = \Drupal::moduleHandler()->invokeAllDeprecated('There is no replacement. Use composer to manage the code for your site. See https://www.drupal.org/node/3512364', 'updater_info');
|
||||
\Drupal::moduleHandler()->alterDeprecated('There is no replacement. Use composer to manage the code for your site. See https://www.drupal.org/node/3512364', 'updater_info', $updaters);
|
||||
uasort($updaters, [SortArray::class, 'sortByWeightElement']);
|
||||
}
|
||||
return $updaters;
|
||||
}
|
||||
|
||||
/**
|
||||
* Assembles the Drupal FileTransfer registry.
|
||||
*
|
||||
* @return array
|
||||
* The Drupal FileTransfer class registry.
|
||||
*
|
||||
* @see \Drupal\Core\FileTransfer\FileTransfer
|
||||
* @see hook_filetransfer_info()
|
||||
* @see hook_filetransfer_info_alter()
|
||||
*/
|
||||
function drupal_get_filetransfer_info() {
|
||||
@trigger_error(__FUNCTION__ . '() is deprecated in drupal:11.2.0 and is removed from drupal:12.0.0. There is no replacement. Use composer to manage the code for your site. See https://www.drupal.org/node/3512364', E_USER_DEPRECATED);
|
||||
|
||||
$info = &drupal_static(__FUNCTION__);
|
||||
if (!isset($info)) {
|
||||
$info = \Drupal::moduleHandler()->invokeAllDeprecated('There is no replacement. Use composer to manage the code for your site. See https://www.drupal.org/node/3512364', 'filetransfer_info');
|
||||
\Drupal::moduleHandler()->alterDeprecated('There is no replacement. Use composer to manage the code for your site. See https://www.drupal.org/node/3512364', 'filetransfer_info', $info);
|
||||
uasort($info, [SortArray::class, 'sortByWeightElement']);
|
||||
}
|
||||
return $info;
|
||||
}
|
||||
376
web/core/includes/errors.inc
Normal file
376
web/core/includes/errors.inc
Normal file
@ -0,0 +1,376 @@
|
||||
<?php
|
||||
|
||||
/**
|
||||
* @file
|
||||
* Functions for error handling.
|
||||
*/
|
||||
|
||||
use Drupal\Component\Render\FormattableMarkup;
|
||||
use Drupal\Component\Utility\Xss;
|
||||
use Drupal\Core\Installer\InstallerKernel;
|
||||
use Drupal\Core\Logger\RfcLogLevel;
|
||||
use Drupal\Core\Render\Markup;
|
||||
use Drupal\Core\Utility\Error;
|
||||
use Symfony\Component\HttpFoundation\Response;
|
||||
|
||||
/**
|
||||
* Maps PHP error constants to watchdog severity levels.
|
||||
*
|
||||
* The error constants are documented at
|
||||
* http://php.net/manual/errorfunc.constants.php
|
||||
*
|
||||
* @ingroup logging_severity_levels
|
||||
*/
|
||||
function drupal_error_levels(): array {
|
||||
$types = [
|
||||
E_ERROR => ['Error', RfcLogLevel::ERROR],
|
||||
E_WARNING => ['Warning', RfcLogLevel::WARNING],
|
||||
E_PARSE => ['Parse error', RfcLogLevel::ERROR],
|
||||
E_NOTICE => ['Notice', RfcLogLevel::NOTICE],
|
||||
E_CORE_ERROR => ['Core error', RfcLogLevel::ERROR],
|
||||
E_CORE_WARNING => ['Core warning', RfcLogLevel::WARNING],
|
||||
E_COMPILE_ERROR => ['Compile error', RfcLogLevel::ERROR],
|
||||
E_COMPILE_WARNING => ['Compile warning', RfcLogLevel::WARNING],
|
||||
E_USER_ERROR => ['User error', RfcLogLevel::ERROR],
|
||||
E_USER_WARNING => ['User warning', RfcLogLevel::WARNING],
|
||||
E_USER_NOTICE => ['User notice', RfcLogLevel::NOTICE],
|
||||
E_RECOVERABLE_ERROR => ['Recoverable fatal error', RfcLogLevel::ERROR],
|
||||
E_DEPRECATED => ['Deprecated function', RfcLogLevel::DEBUG],
|
||||
E_USER_DEPRECATED => ['User deprecated function', RfcLogLevel::DEBUG],
|
||||
];
|
||||
|
||||
return $types;
|
||||
}
|
||||
|
||||
/**
|
||||
* Provides custom PHP error handling.
|
||||
*
|
||||
* @param int $error_level
|
||||
* The level of the error raised.
|
||||
* @param string $message
|
||||
* The error message.
|
||||
* @param string $filename
|
||||
* The filename that the error was raised in.
|
||||
* @param int $line
|
||||
* The line number the error was raised at.
|
||||
*/
|
||||
function _drupal_error_handler_real($error_level, $message, $filename, $line): void {
|
||||
if ($error_level & error_reporting()) {
|
||||
$types = drupal_error_levels();
|
||||
[$severity_msg, $severity_level] = $types[$error_level];
|
||||
$backtrace = debug_backtrace();
|
||||
$caller = Error::getLastCaller($backtrace);
|
||||
|
||||
// We treat recoverable errors as fatal.
|
||||
$recoverable = $error_level == E_RECOVERABLE_ERROR;
|
||||
// As __toString() methods must not throw exceptions (recoverable errors)
|
||||
// in PHP, we allow them to trigger a fatal error by emitting a user error
|
||||
// using trigger_error().
|
||||
$to_string = $error_level == E_USER_ERROR && str_ends_with($caller['function'], '__toString()');
|
||||
_drupal_log_error([
|
||||
'%type' => isset($types[$error_level]) ? $severity_msg : 'Unknown error',
|
||||
// The standard PHP error handler considers that the error messages
|
||||
// are HTML. We mimic this behavior here.
|
||||
'@message' => Markup::create(Xss::filterAdmin($message)),
|
||||
'%function' => $caller['function'],
|
||||
'%file' => $caller['file'],
|
||||
'%line' => $caller['line'],
|
||||
'severity_level' => $severity_level,
|
||||
'backtrace' => $backtrace,
|
||||
'@backtrace_string' => (new \Exception())->getTraceAsString(),
|
||||
'exception' => NULL,
|
||||
], $recoverable || $to_string);
|
||||
}
|
||||
// If the site is a test site then fail for user deprecations so they can be
|
||||
// caught by the deprecation error handler.
|
||||
elseif (defined('DRUPAL_TEST_IN_CHILD_SITE') && DRUPAL_TEST_IN_CHILD_SITE && $error_level === E_USER_DEPRECATED) {
|
||||
static $seen = [];
|
||||
if (array_search($message, $seen, TRUE) === FALSE) {
|
||||
// Only report each deprecation once. Too many headers can break some
|
||||
// Chrome and web driver testing.
|
||||
$seen[] = $message;
|
||||
$backtrace = debug_backtrace();
|
||||
$caller = Error::getLastCaller($backtrace);
|
||||
_drupal_error_header(
|
||||
Markup::create(Xss::filterAdmin($message)),
|
||||
'User deprecated function',
|
||||
$caller['function'],
|
||||
$caller['file'],
|
||||
$caller['line']
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Determines whether an error should be displayed.
|
||||
*
|
||||
* When in maintenance mode or when error_level is ERROR_REPORTING_DISPLAY_ALL,
|
||||
* all errors should be displayed. For ERROR_REPORTING_DISPLAY_SOME, $error
|
||||
* will be examined to determine if it should be displayed.
|
||||
*
|
||||
* @param array $error
|
||||
* Optional error to examine for ERROR_REPORTING_DISPLAY_SOME.
|
||||
*
|
||||
* @return bool
|
||||
* TRUE if an error should be displayed.
|
||||
*/
|
||||
function error_displayable($error = NULL) {
|
||||
if (defined('MAINTENANCE_MODE')) {
|
||||
return TRUE;
|
||||
}
|
||||
$error_level = _drupal_get_error_level();
|
||||
if ($error_level == ERROR_REPORTING_DISPLAY_ALL || $error_level == ERROR_REPORTING_DISPLAY_VERBOSE) {
|
||||
return TRUE;
|
||||
}
|
||||
if ($error_level == ERROR_REPORTING_DISPLAY_SOME && isset($error)) {
|
||||
return $error['%type'] != 'Notice' && $error['%type'] != 'Strict warning';
|
||||
}
|
||||
return FALSE;
|
||||
}
|
||||
|
||||
/**
|
||||
* Logs a PHP error or exception and displays an error page in fatal cases.
|
||||
*
|
||||
* @param array $error
|
||||
* An array with the following keys: %type, @message, %function, %file, %line,
|
||||
* @backtrace_string, severity_level, backtrace, and exception. All the
|
||||
* parameters are plain-text, with the exception of @message, which needs to
|
||||
* be an HTML string, backtrace, which is a standard PHP backtrace, and
|
||||
* exception, which is the exception object (or NULL if the error is not an
|
||||
* exception).
|
||||
* @param bool $fatal
|
||||
* TRUE for:
|
||||
* - An exception is thrown and not caught by something else.
|
||||
* - A recoverable fatal error, which is a fatal error.
|
||||
* Non-recoverable fatal errors cannot be logged by Drupal.
|
||||
*/
|
||||
function _drupal_log_error($error, $fatal = FALSE): void {
|
||||
$is_installer = InstallerKernel::installationAttempted() && \Drupal::hasContainer();
|
||||
|
||||
// Backtrace, exception and 'severity_level' are not valid replacement values
|
||||
// for t().
|
||||
$backtrace = $error['backtrace'];
|
||||
$exception = $error['exception'];
|
||||
$severity = $error['severity_level'];
|
||||
unset($error['backtrace'], $error['exception'], $error['severity_level']);
|
||||
|
||||
// When running inside the testing framework, we relay the errors
|
||||
// to the tested site by the way of HTTP headers.
|
||||
if (defined('DRUPAL_TEST_IN_CHILD_SITE') && DRUPAL_TEST_IN_CHILD_SITE && !headers_sent() && (!defined('SIMPLETEST_COLLECT_ERRORS') || SIMPLETEST_COLLECT_ERRORS)) {
|
||||
_drupal_error_header($error['@message'], $error['%type'], $error['%function'], $error['%file'], $error['%line']);
|
||||
}
|
||||
|
||||
$response = new Response();
|
||||
|
||||
// Only call the logger if there is a logger factory available. This can occur
|
||||
// if there is an error while rebuilding the container or during the
|
||||
// installer.
|
||||
if (\Drupal::hasService('logger.factory')) {
|
||||
try {
|
||||
// Provide the PHP backtrace and exception to logger implementations. Add
|
||||
// 'severity_level' to the context to maintain BC and allow logging
|
||||
// implementations to use it.
|
||||
\Drupal::logger('php')->log($severity, '%type: @message in %function (line %line of %file) @backtrace_string.', $error + ['backtrace' => $backtrace, 'exception' => $exception, 'severity_level' => $severity]);
|
||||
}
|
||||
catch (\Throwable) {
|
||||
// We can't log, for example because the database connection is not
|
||||
// available. At least try to log to PHP error log.
|
||||
error_log(strtr('Failed to log error: ' . Error::DEFAULT_ERROR_MESSAGE . ' @backtrace_string', $error));
|
||||
}
|
||||
}
|
||||
|
||||
// Log fatal errors, so developers can find and debug them.
|
||||
if ($fatal) {
|
||||
error_log(sprintf('%s: %s in %s on line %d %s', $error['%type'], $error['@message'], $error['%file'], $error['%line'], $error['@backtrace_string']));
|
||||
}
|
||||
|
||||
if (PHP_SAPI === 'cli') {
|
||||
if ($fatal) {
|
||||
// When called from CLI, simply output a plain text message.
|
||||
// Should not translate the string to avoid errors producing more errors.
|
||||
$response->setContent(html_entity_decode(strip_tags(new FormattableMarkup(Error::DEFAULT_ERROR_MESSAGE, $error))) . "\n");
|
||||
$response->send();
|
||||
exit(1);
|
||||
}
|
||||
}
|
||||
|
||||
if (\Drupal::hasRequest() && \Drupal::request()->isXmlHttpRequest()) {
|
||||
if ($fatal) {
|
||||
if (error_displayable($error)) {
|
||||
// When called from JavaScript, simply output the error message. Should
|
||||
// not translate the string to avoid errors producing more errors.
|
||||
$response->setContent(new FormattableMarkup(Error::DEFAULT_ERROR_MESSAGE, $error));
|
||||
$response->send();
|
||||
}
|
||||
exit;
|
||||
}
|
||||
}
|
||||
else {
|
||||
// Display the message if the current error reporting level allows this type
|
||||
// of message to be displayed, and unconditionally in update.php.
|
||||
$message = '';
|
||||
$class = NULL;
|
||||
if (error_displayable($error)) {
|
||||
$class = 'error';
|
||||
|
||||
// If error type is 'User notice' then treat it as debug information
|
||||
// instead of an error message.
|
||||
if ($error['%type'] == 'User notice') {
|
||||
$error['%type'] = 'Debug';
|
||||
$class = 'status';
|
||||
}
|
||||
|
||||
// Attempt to reduce verbosity by removing DRUPAL_ROOT from the file path
|
||||
// in the message. This also prevents full path disclosure, see
|
||||
// https://owasp.org/www-community/attacks/Full_Path_Disclosure.
|
||||
try {
|
||||
$root = \Drupal::root();
|
||||
}
|
||||
catch (\Throwable) {
|
||||
$root = realpath(dirname(__DIR__, 2));
|
||||
}
|
||||
if (str_starts_with($error['%file'], $root)) {
|
||||
$error['%file'] = substr($error['%file'], strlen($root) + 1);
|
||||
}
|
||||
|
||||
// Check if verbose error reporting is on.
|
||||
$error_level = _drupal_get_error_level();
|
||||
|
||||
if ($error_level != ERROR_REPORTING_DISPLAY_VERBOSE) {
|
||||
// Without verbose logging, use a simple message.
|
||||
|
||||
// We use \Drupal\Component\Render\FormattableMarkup directly here,
|
||||
// rather than use t() since we are in the middle of error handling, and
|
||||
// we don't want t() to cause further errors.
|
||||
$message = new FormattableMarkup(Error::DEFAULT_ERROR_MESSAGE, $error);
|
||||
}
|
||||
else {
|
||||
// With verbose logging, we will also include a backtrace.
|
||||
// First trace is the error itself, already contained in the message.
|
||||
array_shift($backtrace);
|
||||
// Strip arguments from the backtrace.
|
||||
$error['@backtrace'] = Error::formatBacktrace(array_map(function ($trace) {
|
||||
unset($trace['args']);
|
||||
return $trace;
|
||||
}, $backtrace));
|
||||
$message = new FormattableMarkup('<details class="error-with-backtrace"><summary>' . Error::DEFAULT_ERROR_MESSAGE . '</summary><pre class="backtrace">@backtrace</pre></details>', $error);
|
||||
}
|
||||
}
|
||||
|
||||
if ($fatal) {
|
||||
// We fallback to a maintenance page at this point, because the page
|
||||
// generation itself can generate errors.
|
||||
// Should not translate the string to avoid errors producing more errors.
|
||||
$message = 'The website encountered an unexpected error. Try again later.<br />' . $message;
|
||||
|
||||
if ($is_installer) {
|
||||
// install_display_output() prints the output and ends script execution.
|
||||
$output = [
|
||||
'#title' => 'Error',
|
||||
'#markup' => $message,
|
||||
];
|
||||
try {
|
||||
install_display_output($output, $GLOBALS['install_state']);
|
||||
exit;
|
||||
}
|
||||
catch (\Throwable) {
|
||||
// The maintenance page failed, so fall back to a plain error message.
|
||||
}
|
||||
}
|
||||
|
||||
$response->setContent($message);
|
||||
$response->setStatusCode(500, '500 Service unavailable (with message)');
|
||||
|
||||
$response->send();
|
||||
// An exception must halt script execution.
|
||||
exit;
|
||||
}
|
||||
|
||||
if ($message) {
|
||||
if (\Drupal::hasService('session')) {
|
||||
// Message display is dependent on sessions being available.
|
||||
\Drupal::messenger()->addMessage($message, $class, TRUE);
|
||||
}
|
||||
else {
|
||||
print $message;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the current error level.
|
||||
*
|
||||
* This function should only be used to get the current error level prior to the
|
||||
* kernel being booted or before Drupal is installed. In all other situations
|
||||
* the following code is preferred:
|
||||
* @code
|
||||
* \Drupal::config('system.logging')->get('error_level');
|
||||
* @endcode
|
||||
*
|
||||
* @return string
|
||||
* The current error level.
|
||||
*/
|
||||
function _drupal_get_error_level() {
|
||||
// Raise the error level to maximum for the installer, so users are able to
|
||||
// file proper bug reports for installer errors. The returned value is
|
||||
// different to the one below, because the installer actually has a
|
||||
// 'config.factory' service, which reads the default 'error_level' value from
|
||||
// System module's default configuration and the default value is not verbose.
|
||||
// @see error_displayable()
|
||||
if (InstallerKernel::installationAttempted()) {
|
||||
return ERROR_REPORTING_DISPLAY_VERBOSE;
|
||||
}
|
||||
$error_level = NULL;
|
||||
// Try to get the error level configuration from database. If this fails,
|
||||
// for example if the database connection is not there, try to read it from
|
||||
// settings.php.
|
||||
try {
|
||||
$error_level = \Drupal::config('system.logging')->get('error_level');
|
||||
}
|
||||
catch (\Exception) {
|
||||
$error_level = $GLOBALS['config']['system.logging']['error_level'] ?? ERROR_REPORTING_HIDE;
|
||||
}
|
||||
|
||||
// If there is no container or if it has no config.factory service, we are
|
||||
// possibly in an edge-case error situation while trying to serve a regular
|
||||
// request on a public site, so use the non-verbose default value.
|
||||
return $error_level ?: ERROR_REPORTING_DISPLAY_ALL;
|
||||
}
|
||||
|
||||
/**
|
||||
* Adds error information to headers so that tests can access it.
|
||||
*
|
||||
* @param string $message
|
||||
* The error message.
|
||||
* @param string $type
|
||||
* The type of error.
|
||||
* @param string $function
|
||||
* The function that emitted the error.
|
||||
* @param string $file
|
||||
* The file that emitted the error.
|
||||
* @param int $line
|
||||
* The line number in file that emitted the error.
|
||||
*/
|
||||
function _drupal_error_header($message, $type, $function, $file, $line): void {
|
||||
// $number does not use drupal_static as it should not be reset
|
||||
// as it uniquely identifies each PHP error.
|
||||
static $number = 0;
|
||||
$assertion = [
|
||||
$message,
|
||||
$type,
|
||||
[
|
||||
'function' => $function,
|
||||
'file' => $file,
|
||||
'line' => $line,
|
||||
],
|
||||
];
|
||||
// For non-fatal errors (e.g. PHP notices) _drupal_log_error can be called
|
||||
// multiple times per request. In that case the response is typically
|
||||
// generated outside of the error handler, e.g., in a controller. As a
|
||||
// result it is not possible to use a Response object here but instead the
|
||||
// headers need to be emitted directly.
|
||||
header('X-Drupal-Assertion-' . $number . ': ' . rawurlencode(serialize($assertion)));
|
||||
$number++;
|
||||
}
|
||||
1048
web/core/includes/form.inc
Normal file
1048
web/core/includes/form.inc
Normal file
File diff suppressed because it is too large
Load Diff
2659
web/core/includes/install.core.inc
Normal file
2659
web/core/includes/install.core.inc
Normal file
File diff suppressed because it is too large
Load Diff
869
web/core/includes/install.inc
Normal file
869
web/core/includes/install.inc
Normal file
@ -0,0 +1,869 @@
|
||||
<?php
|
||||
|
||||
/**
|
||||
* @file
|
||||
* API functions for installing modules and themes.
|
||||
*/
|
||||
|
||||
use Drupal\Component\Utility\Unicode;
|
||||
use Drupal\Component\Utility\UrlHelper;
|
||||
use Drupal\Core\Database\Database;
|
||||
use Drupal\Core\Extension\Dependency;
|
||||
use Drupal\Core\Extension\Extension;
|
||||
use Drupal\Core\Extension\ExtensionDiscovery;
|
||||
use Drupal\Core\Extension\InstallRequirementsInterface;
|
||||
use Drupal\Core\Extension\Requirement\RequirementSeverity;
|
||||
use Drupal\Core\Installer\InstallerKernel;
|
||||
use Symfony\Component\HttpFoundation\RedirectResponse;
|
||||
|
||||
/**
|
||||
* Requirement severity -- Informational message only.
|
||||
*
|
||||
* @deprecated in drupal:11.2.0 and is removed from drupal:12.0.0. Use
|
||||
* \Drupal\Core\Extension\Requirement\RequirementSeverity::Info instead.
|
||||
*
|
||||
* @see https://www.drupal.org/node/3410939
|
||||
*/
|
||||
const REQUIREMENT_INFO = -1;
|
||||
|
||||
/**
|
||||
* Requirement severity -- Requirement successfully met.
|
||||
*
|
||||
* @deprecated in drupal:11.2.0 and is removed from drupal:12.0.0. Use
|
||||
* \Drupal\Core\Extension\Requirement\RequirementSeverity::OK instead.
|
||||
*
|
||||
* @see https://www.drupal.org/node/3410939
|
||||
*/
|
||||
const REQUIREMENT_OK = 0;
|
||||
|
||||
/**
|
||||
* Requirement severity -- Warning condition; proceed but flag warning.
|
||||
*
|
||||
* @deprecated in drupal:11.2.0 and is removed from drupal:12.0.0. Use
|
||||
* \Drupal\Core\Extension\Requirement\RequirementSeverity::Warning instead.
|
||||
*
|
||||
* @see https://www.drupal.org/node/3410939
|
||||
*/
|
||||
const REQUIREMENT_WARNING = 1;
|
||||
|
||||
/**
|
||||
* Requirement severity -- Error condition; abort installation.
|
||||
*
|
||||
* @deprecated in drupal:11.2.0 and is removed from drupal:12.0.0. Use
|
||||
* \Drupal\Core\Extension\Requirement\RequirementSeverity::Error instead.
|
||||
*
|
||||
* @see https://www.drupal.org/node/3410939
|
||||
*/
|
||||
const REQUIREMENT_ERROR = 2;
|
||||
|
||||
/**
|
||||
* File permission check -- File exists.
|
||||
*/
|
||||
const FILE_EXIST = 1;
|
||||
|
||||
/**
|
||||
* File permission check -- File is readable.
|
||||
*/
|
||||
const FILE_READABLE = 2;
|
||||
|
||||
/**
|
||||
* File permission check -- File is writable.
|
||||
*/
|
||||
const FILE_WRITABLE = 4;
|
||||
|
||||
/**
|
||||
* File permission check -- File is executable.
|
||||
*/
|
||||
const FILE_EXECUTABLE = 8;
|
||||
|
||||
/**
|
||||
* File permission check -- File does not exist.
|
||||
*/
|
||||
const FILE_NOT_EXIST = 16;
|
||||
|
||||
/**
|
||||
* File permission check -- File is not readable.
|
||||
*/
|
||||
const FILE_NOT_READABLE = 32;
|
||||
|
||||
/**
|
||||
* File permission check -- File is not writable.
|
||||
*/
|
||||
const FILE_NOT_WRITABLE = 64;
|
||||
|
||||
/**
|
||||
* File permission check -- File is not executable.
|
||||
*/
|
||||
const FILE_NOT_EXECUTABLE = 128;
|
||||
|
||||
/**
|
||||
* Loads .install files for installed modules to initialize the update system.
|
||||
*/
|
||||
function drupal_load_updates(): void {
|
||||
/** @var \Drupal\Core\Extension\ModuleExtensionList $extension_list_module */
|
||||
$extension_list_module = \Drupal::service('extension.list.module');
|
||||
foreach (\Drupal::service('update.update_hook_registry')->getAllInstalledVersions() as $module => $schema_version) {
|
||||
if ($extension_list_module->exists($module) && !$extension_list_module->checkIncompatibility($module)) {
|
||||
if ($schema_version > -1) {
|
||||
\Drupal::moduleHandler()->loadInclude($module, 'install');
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Loads the installation profile, extracting its defined distribution name.
|
||||
*
|
||||
* @return string
|
||||
* The distribution name defined in the profile's .info.yml file. Defaults to
|
||||
* "Drupal" if none is explicitly provided by the installation profile.
|
||||
*
|
||||
* @see install_profile_info()
|
||||
*/
|
||||
function drupal_install_profile_distribution_name() {
|
||||
// During installation, the profile information is stored in the global
|
||||
// installation state (it might not be saved anywhere yet).
|
||||
$info = [];
|
||||
if (InstallerKernel::installationAttempted()) {
|
||||
global $install_state;
|
||||
if (isset($install_state['profile_info'])) {
|
||||
$info = $install_state['profile_info'];
|
||||
}
|
||||
}
|
||||
// At all other times, we load the profile via standard methods.
|
||||
elseif ($profile = \Drupal::installProfile()) {
|
||||
$info = \Drupal::service('extension.list.profile')->getExtensionInfo($profile);
|
||||
}
|
||||
return $info['distribution']['name'] ?? 'Drupal';
|
||||
}
|
||||
|
||||
/**
|
||||
* Loads the installation profile, extracting its defined version.
|
||||
*
|
||||
* @return string
|
||||
* Distribution version defined in the profile's .info.yml file.
|
||||
* Defaults to \Drupal::VERSION if no version is explicitly provided by the
|
||||
* installation profile.
|
||||
*
|
||||
* @see install_profile_info()
|
||||
*/
|
||||
function drupal_install_profile_distribution_version() {
|
||||
// During installation, the profile information is stored in the global
|
||||
// installation state (it might not be saved anywhere yet).
|
||||
if (InstallerKernel::installationAttempted()) {
|
||||
global $install_state;
|
||||
return $install_state['profile_info']['version'] ?? \Drupal::VERSION;
|
||||
}
|
||||
// At all other times, we load the profile via standard methods.
|
||||
else {
|
||||
$profile = \Drupal::installProfile();
|
||||
$info = \Drupal::service('extension.list.profile')->getExtensionInfo($profile);
|
||||
return $info['version'];
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Verifies that all dependencies are met for a given installation profile.
|
||||
*
|
||||
* @param array $install_state
|
||||
* An array of information about the current installation state.
|
||||
*
|
||||
* @return array
|
||||
* The list of modules to install.
|
||||
*
|
||||
* @todo https://www.drupal.org/i/3005959 Rework this method as it is not only
|
||||
* about profiles.
|
||||
*/
|
||||
function drupal_verify_profile($install_state): array {
|
||||
$profile = $install_state['parameters']['profile'];
|
||||
if ($profile === FALSE) {
|
||||
return [];
|
||||
}
|
||||
$info = $install_state['profile_info'];
|
||||
|
||||
// Get the list of available modules for the selected installation profile.
|
||||
$listing = new ExtensionDiscovery(\Drupal::root());
|
||||
$present_modules = [];
|
||||
foreach ($listing->scan('module') as $present_module) {
|
||||
$present_modules[] = $present_module->getName();
|
||||
}
|
||||
|
||||
// The installation profile is also a module, which needs to be installed
|
||||
// after all the other dependencies have been installed.
|
||||
$present_modules[] = $profile;
|
||||
|
||||
// Verify that all of the profile's required modules are present.
|
||||
$missing_modules = array_diff($info['install'], $present_modules);
|
||||
|
||||
$requirements = [];
|
||||
|
||||
if ($missing_modules) {
|
||||
$build = [
|
||||
'#theme' => 'item_list',
|
||||
'#context' => ['list_style' => 'comma-list'],
|
||||
];
|
||||
|
||||
foreach ($missing_modules as $module) {
|
||||
$build['#items'][] = ['#markup' => '<span class="admin-missing">' . Unicode::ucfirst($module) . '</span>'];
|
||||
}
|
||||
|
||||
$modules_list = \Drupal::service('renderer')->renderInIsolation($build);
|
||||
$requirements['required_modules'] = [
|
||||
'title' => t('Required modules'),
|
||||
'value' => t('Required modules not found.'),
|
||||
'severity' => RequirementSeverity::Error,
|
||||
'description' => t('The following modules are required but were not found. Move them into the appropriate modules subdirectory, such as <em>/modules</em>. Missing modules: @modules', ['@modules' => $modules_list]),
|
||||
];
|
||||
}
|
||||
return $requirements;
|
||||
}
|
||||
|
||||
/**
|
||||
* Installs the system module.
|
||||
*
|
||||
* Separated from the installation of other modules so core system
|
||||
* functions can be made available while other modules are installed.
|
||||
*
|
||||
* @param array $install_state
|
||||
* An array of information about the current installation state. This is used
|
||||
* to set the default language.
|
||||
*/
|
||||
function drupal_install_system($install_state): void {
|
||||
// Remove the service provider of the early installer.
|
||||
unset($GLOBALS['conf']['container_service_providers']['InstallerServiceProvider']);
|
||||
// Add the normal installer service provider.
|
||||
$GLOBALS['conf']['container_service_providers']['InstallerServiceProvider'] = 'Drupal\Core\Installer\NormalInstallerServiceProvider';
|
||||
|
||||
// Get the existing request.
|
||||
$request = \Drupal::request();
|
||||
// Reboot into a full production environment to continue the installation.
|
||||
/** @var \Drupal\Core\Installer\InstallerKernel $kernel */
|
||||
$kernel = \Drupal::service('kernel');
|
||||
$kernel->shutdown();
|
||||
// Have installer rebuild from the disk, rather then building from scratch.
|
||||
$kernel->rebuildContainer();
|
||||
// Reboot the kernel with new container.
|
||||
$kernel->boot();
|
||||
$kernel->preHandle($request);
|
||||
// Ensure our request includes the session if appropriate.
|
||||
if (PHP_SAPI !== 'cli') {
|
||||
$request->setSession($kernel->getContainer()->get('session'));
|
||||
}
|
||||
|
||||
// Before having installed the system module and being able to do a module
|
||||
// rebuild, prime the \Drupal\Core\Extension\ModuleExtensionList static cache
|
||||
// with the module's location.
|
||||
// @todo Try to install system as any other module, see
|
||||
// https://www.drupal.org/node/2719315.
|
||||
\Drupal::service('extension.list.module')->setPathname('system', 'core/modules/system/system.info.yml');
|
||||
|
||||
// Install base system configuration.
|
||||
\Drupal::service('config.installer')->installDefaultConfig('core', 'core');
|
||||
|
||||
// Store the installation profile in configuration to populate the
|
||||
// 'install_profile' container parameter.
|
||||
$config = \Drupal::configFactory()->getEditable('core.extension');
|
||||
if ($install_state['parameters']['profile'] === FALSE) {
|
||||
$config->clear('profile');
|
||||
}
|
||||
else {
|
||||
$config->set('profile', $install_state['parameters']['profile']);
|
||||
}
|
||||
$config->save();
|
||||
|
||||
$connection = Database::getConnection();
|
||||
$provider = $connection->getProvider();
|
||||
// When the database driver is provided by a module, then install that module.
|
||||
// This module must be installed before any other module, as it must be able
|
||||
// to override any call to hook_schema() or any "backend_overridable" service.
|
||||
// In edge cases, a driver module may extend from another driver module (for
|
||||
// instance, a module to provide backward compatibility with a database
|
||||
// version no longer supported by core). In order for the extended classes to
|
||||
// be autoloadable, the extending module should list the extended module in
|
||||
// its dependencies, and here the dependencies will be installed as well.
|
||||
if ($provider !== 'core') {
|
||||
$autoload = $connection->getConnectionOptions()['autoload'] ?? '';
|
||||
if (str_contains($autoload, 'src/Driver/Database/')) {
|
||||
$kernel->getContainer()->get('module_installer')->install([$provider], TRUE);
|
||||
}
|
||||
}
|
||||
|
||||
// Install System module.
|
||||
$kernel->getContainer()->get('module_installer')->install(['system'], FALSE);
|
||||
|
||||
// Ensure default language is saved.
|
||||
if (isset($install_state['parameters']['langcode'])) {
|
||||
\Drupal::configFactory()->getEditable('system.site')
|
||||
->set('langcode', (string) $install_state['parameters']['langcode'])
|
||||
->set('default_langcode', (string) $install_state['parameters']['langcode'])
|
||||
->save(TRUE);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Verifies the state of the specified file.
|
||||
*
|
||||
* @param string $file
|
||||
* The file to check for.
|
||||
* @param int $mask
|
||||
* An optional bitmask created from various FILE_* constants.
|
||||
* @param string|null $type
|
||||
* The type of file. Can be file (default), dir, or link.
|
||||
* @param bool $auto_fix
|
||||
* (optional) Determines whether to attempt fixing the permissions according
|
||||
* to the provided $mask. Defaults to TRUE.
|
||||
*
|
||||
* @return bool
|
||||
* TRUE on success or FALSE on failure. A message is set for the latter.
|
||||
*/
|
||||
function drupal_verify_install_file($file, $mask = NULL, $type = 'file', $auto_fix = TRUE) {
|
||||
$return = TRUE;
|
||||
// Check for files that shouldn't be there.
|
||||
if (isset($mask) && ($mask & FILE_NOT_EXIST) && file_exists($file)) {
|
||||
return FALSE;
|
||||
}
|
||||
// Verify that the file is the type of file it is supposed to be.
|
||||
if (isset($type) && file_exists($file)) {
|
||||
$check = 'is_' . $type;
|
||||
if (!function_exists($check) || !$check($file)) {
|
||||
$return = FALSE;
|
||||
}
|
||||
}
|
||||
|
||||
// Verify file permissions.
|
||||
if (isset($mask)) {
|
||||
$masks = [FILE_EXIST, FILE_READABLE, FILE_WRITABLE, FILE_EXECUTABLE, FILE_NOT_READABLE, FILE_NOT_WRITABLE, FILE_NOT_EXECUTABLE];
|
||||
foreach ($masks as $current_mask) {
|
||||
if ($mask & $current_mask) {
|
||||
switch ($current_mask) {
|
||||
case FILE_EXIST:
|
||||
if (!file_exists($file)) {
|
||||
if ($type == 'dir' && $auto_fix) {
|
||||
drupal_install_mkdir($file, $mask);
|
||||
}
|
||||
if (!file_exists($file)) {
|
||||
$return = FALSE;
|
||||
}
|
||||
}
|
||||
break;
|
||||
|
||||
case FILE_READABLE:
|
||||
if (!is_readable($file)) {
|
||||
$return = FALSE;
|
||||
}
|
||||
break;
|
||||
|
||||
case FILE_WRITABLE:
|
||||
if (!is_writable($file)) {
|
||||
$return = FALSE;
|
||||
}
|
||||
break;
|
||||
|
||||
case FILE_EXECUTABLE:
|
||||
if (!is_executable($file)) {
|
||||
$return = FALSE;
|
||||
}
|
||||
break;
|
||||
|
||||
case FILE_NOT_READABLE:
|
||||
if (is_readable($file)) {
|
||||
$return = FALSE;
|
||||
}
|
||||
break;
|
||||
|
||||
case FILE_NOT_WRITABLE:
|
||||
if (is_writable($file)) {
|
||||
$return = FALSE;
|
||||
}
|
||||
break;
|
||||
|
||||
case FILE_NOT_EXECUTABLE:
|
||||
if (is_executable($file)) {
|
||||
$return = FALSE;
|
||||
}
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
if (!$return && $auto_fix) {
|
||||
return drupal_install_fix_file($file, $mask);
|
||||
}
|
||||
return $return;
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a directory with the specified permissions.
|
||||
*
|
||||
* @param string $file
|
||||
* The name of the directory to create.
|
||||
* @param int $mask
|
||||
* The permissions of the directory to create.
|
||||
* @param bool $message
|
||||
* (optional) Whether to output messages. Defaults to TRUE.
|
||||
*
|
||||
* @return bool
|
||||
* TRUE/FALSE whether or not the directory was successfully created.
|
||||
*/
|
||||
function drupal_install_mkdir($file, $mask, $message = TRUE) {
|
||||
$mod = 0;
|
||||
$masks = [FILE_READABLE, FILE_WRITABLE, FILE_EXECUTABLE, FILE_NOT_READABLE, FILE_NOT_WRITABLE, FILE_NOT_EXECUTABLE];
|
||||
foreach ($masks as $m) {
|
||||
if ($mask & $m) {
|
||||
switch ($m) {
|
||||
case FILE_READABLE:
|
||||
$mod |= 0444;
|
||||
break;
|
||||
|
||||
case FILE_WRITABLE:
|
||||
$mod |= 0222;
|
||||
break;
|
||||
|
||||
case FILE_EXECUTABLE:
|
||||
$mod |= 0111;
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (@\Drupal::service('file_system')->mkdir($file, $mod)) {
|
||||
return TRUE;
|
||||
}
|
||||
else {
|
||||
return FALSE;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Attempts to fix file permissions.
|
||||
*
|
||||
* The general approach here is that, because we do not know the security
|
||||
* setup of the webserver, we apply our permission changes to all three
|
||||
* digits of the file permission (i.e. user, group and all).
|
||||
*
|
||||
* To ensure that the values behave as expected (and numbers don't carry
|
||||
* from one digit to the next) we do the calculation on the octal value
|
||||
* using bitwise operations. This lets us remove, for example, 0222 from
|
||||
* 0700 and get the correct value of 0500.
|
||||
*
|
||||
* @param string $file
|
||||
* The name of the file with permissions to fix.
|
||||
* @param int $mask
|
||||
* The desired permissions for the file.
|
||||
* @param bool $message
|
||||
* (optional) Whether to output messages. Defaults to TRUE.
|
||||
*
|
||||
* @return bool
|
||||
* TRUE/FALSE whether or not we were able to fix the file's permissions.
|
||||
*/
|
||||
function drupal_install_fix_file($file, $mask, $message = TRUE) {
|
||||
// If $file does not exist, fileperms() issues a PHP warning.
|
||||
if (!file_exists($file)) {
|
||||
return FALSE;
|
||||
}
|
||||
|
||||
$mod = fileperms($file) & 0777;
|
||||
$masks = [FILE_READABLE, FILE_WRITABLE, FILE_EXECUTABLE, FILE_NOT_READABLE, FILE_NOT_WRITABLE, FILE_NOT_EXECUTABLE];
|
||||
|
||||
// FILE_READABLE, FILE_WRITABLE, and FILE_EXECUTABLE permission strings
|
||||
// can theoretically be 0400, 0200, and 0100 respectively, but to be safe
|
||||
// we set all three access types in case the administrator intends to
|
||||
// change the owner of settings.php after installation.
|
||||
foreach ($masks as $m) {
|
||||
if ($mask & $m) {
|
||||
switch ($m) {
|
||||
case FILE_READABLE:
|
||||
if (!is_readable($file)) {
|
||||
$mod |= 0444;
|
||||
}
|
||||
break;
|
||||
|
||||
case FILE_WRITABLE:
|
||||
if (!is_writable($file)) {
|
||||
$mod |= 0222;
|
||||
}
|
||||
break;
|
||||
|
||||
case FILE_EXECUTABLE:
|
||||
if (!is_executable($file)) {
|
||||
$mod |= 0111;
|
||||
}
|
||||
break;
|
||||
|
||||
case FILE_NOT_READABLE:
|
||||
if (is_readable($file)) {
|
||||
$mod &= ~0444;
|
||||
}
|
||||
break;
|
||||
|
||||
case FILE_NOT_WRITABLE:
|
||||
if (is_writable($file)) {
|
||||
$mod &= ~0222;
|
||||
}
|
||||
break;
|
||||
|
||||
case FILE_NOT_EXECUTABLE:
|
||||
if (is_executable($file)) {
|
||||
$mod &= ~0111;
|
||||
}
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// chmod() will work if the web server is running as owner of the file.
|
||||
if (@chmod($file, $mod)) {
|
||||
return TRUE;
|
||||
}
|
||||
else {
|
||||
return FALSE;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Sends the user to a different installer page.
|
||||
*
|
||||
* This issues an on-site HTTP redirect. Messages (and errors) are erased.
|
||||
*
|
||||
* @param string $path
|
||||
* An installer path.
|
||||
*/
|
||||
function install_goto($path): void {
|
||||
global $base_url;
|
||||
$headers = [
|
||||
// Not a permanent redirect.
|
||||
'Cache-Control' => 'no-cache',
|
||||
];
|
||||
$response = new RedirectResponse($base_url . '/' . $path, 302, $headers);
|
||||
$response->send();
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the URL of the current script, with modified query parameters.
|
||||
*
|
||||
* This function can be called by low-level scripts (such as install.php and
|
||||
* update.php) and returns the URL of the current script. Existing query
|
||||
* parameters are preserved by default, but new ones can optionally be merged
|
||||
* in.
|
||||
*
|
||||
* This function is used when the script must maintain certain query parameters
|
||||
* over multiple page requests in order to work correctly. In such cases (for
|
||||
* example, update.php, which requires the 'continue=1' parameter to remain in
|
||||
* the URL throughout the update process if there are any requirement warnings
|
||||
* that need to be bypassed), using this function to generate the URL for links
|
||||
* to the next steps of the script ensures that the links will work correctly.
|
||||
*
|
||||
* @param array $query
|
||||
* (optional) An array of query parameters to merge in to the existing ones.
|
||||
*
|
||||
* @return string
|
||||
* The URL of the current script, with query parameters modified by the
|
||||
* passed-in $query. The URL is not sanitized, so it still needs to be run
|
||||
* through \Drupal\Component\Utility\UrlHelper::filterBadProtocol() if it will
|
||||
* be used as an HTML attribute value.
|
||||
*
|
||||
* @see drupal_requirements_url()
|
||||
* @see Drupal\Component\Utility\UrlHelper::filterBadProtocol()
|
||||
*/
|
||||
function drupal_current_script_url($query = []) {
|
||||
$uri = $_SERVER['SCRIPT_NAME'];
|
||||
$query = array_merge(UrlHelper::filterQueryParameters(\Drupal::request()->query->all()), $query);
|
||||
if (!empty($query)) {
|
||||
$uri .= '?' . UrlHelper::buildQuery($query);
|
||||
}
|
||||
return $uri;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns a URL for proceeding to the next page after a requirements problem.
|
||||
*
|
||||
* This function can be called by low-level scripts (such as install.php and
|
||||
* update.php) and returns a URL that can be used to attempt to proceed to the
|
||||
* next step of the script.
|
||||
*
|
||||
* @param int|\Drupal\Core\Extension\Requirement\RequirementSeverity $severity
|
||||
* The severity of the requirements problem, as returned by
|
||||
* drupal_requirements_severity().
|
||||
*
|
||||
* @return string
|
||||
* A URL for attempting to proceed to the next step of the script. The URL is
|
||||
* not sanitized, so it still needs to be run through
|
||||
* \Drupal\Component\Utility\UrlHelper::filterBadProtocol() if it will be used
|
||||
* as an HTML attribute value.
|
||||
*
|
||||
* @see drupal_current_script_url()
|
||||
* @see \Drupal\Component\Utility\UrlHelper::filterBadProtocol()
|
||||
*/
|
||||
function drupal_requirements_url(/* int|RequirementSeverity */ $severity): string {
|
||||
if (!$severity instanceof RequirementSeverity) {
|
||||
@trigger_error('Passing a type other than ' . RequirementSeverity::class . ' to ' . __FUNCTION__ . '() is deprecated in drupal:11.2.0 and is removed from drupal:12.0.0. Pass a ' . RequirementSeverity::class . ' enum instead. See https://www.drupal.org/node/3410939', E_USER_DEPRECATED);
|
||||
$severity = RequirementSeverity::from($severity);
|
||||
}
|
||||
if (is_null($severity)) {
|
||||
$severity = RequirementSeverity::Info;
|
||||
}
|
||||
$query = [];
|
||||
// If there are no errors, only warnings, append 'continue=1' to the URL so
|
||||
// the user can bypass this screen on the next page load.
|
||||
if ($severity === RequirementSeverity::Warning) {
|
||||
$query['continue'] = 1;
|
||||
}
|
||||
return drupal_current_script_url($query);
|
||||
}
|
||||
|
||||
/**
|
||||
* Checks an installation profile's requirements.
|
||||
*
|
||||
* @param string $profile
|
||||
* Name of installation profile to check.
|
||||
*
|
||||
* @return array
|
||||
* Array of the installation profile's requirements.
|
||||
*/
|
||||
function drupal_check_profile($profile): array {
|
||||
$info = install_profile_info($profile);
|
||||
// Collect requirement testing results.
|
||||
$requirements = [];
|
||||
// Performs an ExtensionDiscovery scan as the system module is unavailable and
|
||||
// we don't yet know where all the modules are located.
|
||||
// @todo Remove as part of https://www.drupal.org/node/2186491
|
||||
$drupal_root = \Drupal::root();
|
||||
$module_list = (new ExtensionDiscovery($drupal_root))->scan('module');
|
||||
|
||||
foreach ($info['install'] as $module) {
|
||||
// If the module is in the module list we know it exists and we can continue
|
||||
// including and registering it.
|
||||
// @see \Drupal\Core\Extension\ExtensionDiscovery::scanDirectory()
|
||||
if (isset($module_list[$module])) {
|
||||
$function = $module . '_requirements';
|
||||
$module_path = $module_list[$module]->getPath();
|
||||
$install_file = "$drupal_root/$module_path/$module.install";
|
||||
|
||||
if (is_file($install_file)) {
|
||||
require_once $install_file;
|
||||
}
|
||||
|
||||
if (function_exists($function)) {
|
||||
$requirements = array_merge($requirements, $function('install'));
|
||||
}
|
||||
|
||||
$requirements = array_merge($requirements, install_check_class_requirements($module_list[$module]));
|
||||
}
|
||||
}
|
||||
|
||||
// Add the profile requirements.
|
||||
$function = $profile . '_requirements';
|
||||
if (function_exists($function)) {
|
||||
$requirements = array_merge($requirements, $function('install'));
|
||||
}
|
||||
|
||||
$extension = \Drupal::service('extension.list.profile')->get($profile);
|
||||
$requirements = array_merge($requirements, install_check_class_requirements($extension));
|
||||
|
||||
return $requirements;
|
||||
}
|
||||
|
||||
/**
|
||||
* Extracts the highest severity from the requirements array.
|
||||
*
|
||||
* @param array $requirements
|
||||
* An array of requirements, in the same format as is returned by
|
||||
* hook_requirements().
|
||||
*
|
||||
* @return int
|
||||
* The highest severity in the array.
|
||||
*
|
||||
* @deprecated in drupal:11.2.0 and is removed from drupal:12.0.0. Use
|
||||
* \Drupal\Core\Extension\Requirement\RequirementSeverity::getMaxSeverity()
|
||||
* instead.
|
||||
*
|
||||
* @see https://www.drupal.org/node/3410939
|
||||
*/
|
||||
function drupal_requirements_severity(&$requirements) {
|
||||
@trigger_error(__FUNCTION__ . '() is deprecated in drupal:11.2.0 and is removed from drupal:12.0.0. Use ' . RequirementSeverity::class . '::maxSeverityFromRequirements() instead. See https://www.drupal.org/node/3410939', E_USER_DEPRECATED);
|
||||
return RequirementSeverity::maxSeverityFromRequirements($requirements)->value;
|
||||
}
|
||||
|
||||
/**
|
||||
* Checks a module's requirements.
|
||||
*
|
||||
* @param string $module
|
||||
* Machine name of module to check.
|
||||
*
|
||||
* @return bool
|
||||
* TRUE or FALSE, depending on whether the requirements are met.
|
||||
*/
|
||||
function drupal_check_module($module) {
|
||||
/** @var \Drupal\Core\Extension\ModuleExtensionList $module_list */
|
||||
$module_list = \Drupal::service('extension.list.module');
|
||||
$extension = $module_list->get($module);
|
||||
$file = \Drupal::root() . '/' . $extension->getPath() . "/$module.install";
|
||||
if (is_file($file)) {
|
||||
require_once $file;
|
||||
}
|
||||
// Check requirements
|
||||
$requirements = \Drupal::moduleHandler()->invoke($module, 'requirements', ['install']) ?? [];
|
||||
$requirements = array_merge($requirements, install_check_class_requirements($extension));
|
||||
|
||||
if (!empty($requirements) && RequirementSeverity::maxSeverityFromRequirements($requirements) === RequirementSeverity::Error) {
|
||||
// Print any error messages
|
||||
foreach ($requirements as $requirement) {
|
||||
if (isset($requirement['severity']) && $requirement['severity'] === RequirementSeverity::Error) {
|
||||
$message = $requirement['description'];
|
||||
if (isset($requirement['value']) && $requirement['value']) {
|
||||
$message = t('@requirements_message (Currently using @item version @version)', ['@requirements_message' => $requirement['description'], '@item' => $requirement['title'], '@version' => $requirement['value']]);
|
||||
}
|
||||
\Drupal::messenger()->addError($message);
|
||||
}
|
||||
}
|
||||
return FALSE;
|
||||
}
|
||||
return TRUE;
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieves information about an installation profile from its .info.yml file.
|
||||
*
|
||||
* The information stored in a profile .info.yml file is similar to that stored
|
||||
* in a normal Drupal module .info.yml file. For example:
|
||||
* - name: The real name of the installation profile for display purposes.
|
||||
* - description: A brief description of the profile.
|
||||
* - dependencies: An array of short names of other modules that this install
|
||||
* profile requires.
|
||||
* - install: An array of shortname of other modules to install that are not
|
||||
* required by this install profile.
|
||||
*
|
||||
* Additional, less commonly-used information that can appear in a
|
||||
* profile.info.yml file but not in a normal Drupal module .info.yml file
|
||||
* includes:
|
||||
*
|
||||
* - distribution: Existence of this key denotes that the installation profile
|
||||
* is intended to be the only eligible choice in a distribution and will be
|
||||
* auto-selected during installation, whereas the installation profile
|
||||
* selection screen will be skipped. If more than one distribution profile is
|
||||
* found then the first one discovered will be selected.
|
||||
* The following subproperties may be set:
|
||||
* - name: The name of the distribution that is being installed, to be shown
|
||||
* throughout the installation process. If omitted,
|
||||
* drupal_install_profile_distribution_name() defaults to 'Drupal'.
|
||||
* - install: Optional parameters to override the installer:
|
||||
* - theme: The machine name of a theme to use in the installer instead of
|
||||
* Drupal's default installer theme.
|
||||
* - finish_url: A destination to visit after the installation of the
|
||||
* distribution is finished
|
||||
*
|
||||
* Note that this function does an expensive file system scan to get info file
|
||||
* information for dependencies. If you only need information from the info
|
||||
* file itself, use
|
||||
* \Drupal::service('extension.list.profile')->getExtensionInfo().
|
||||
*
|
||||
* Example of .info.yml file:
|
||||
* @code
|
||||
* name: Minimal
|
||||
* description: Start fresh, with only a few modules enabled.
|
||||
* install:
|
||||
* - block
|
||||
* - dblog
|
||||
* @endcode
|
||||
*
|
||||
* @param string $profile
|
||||
* Name of profile.
|
||||
* @param string $langcode
|
||||
* Language code (if any).
|
||||
*
|
||||
* @return array
|
||||
* The info array.
|
||||
*/
|
||||
function install_profile_info($profile, $langcode = 'en') {
|
||||
static $cache = [];
|
||||
|
||||
if (!isset($cache[$profile][$langcode])) {
|
||||
// Set defaults for module info.
|
||||
$defaults = [
|
||||
'dependencies' => [],
|
||||
'install' => [],
|
||||
'themes' => ['stark'],
|
||||
'description' => '',
|
||||
'version' => NULL,
|
||||
'hidden' => FALSE,
|
||||
'php' => \Drupal::MINIMUM_PHP,
|
||||
'config_install_path' => NULL,
|
||||
];
|
||||
$profile_path = \Drupal::service('extension.list.profile')->getPath($profile);
|
||||
/** @var \Drupal\Core\Extension\InfoParserInterface $parser */
|
||||
$parser = \Drupal::service('info_parser');
|
||||
$info = $parser->parse("$profile_path/$profile.info.yml");
|
||||
$info += $defaults;
|
||||
|
||||
$dependency_name_function = function ($dependency) {
|
||||
return Dependency::createFromString($dependency)->getName();
|
||||
};
|
||||
// Convert dependencies in [project:module] format.
|
||||
$info['dependencies'] = array_map($dependency_name_function, $info['dependencies']);
|
||||
|
||||
// Convert install key in [project:module] format.
|
||||
$info['install'] = array_map($dependency_name_function, $info['install']);
|
||||
|
||||
// Get a list of core's required modules.
|
||||
$required = [];
|
||||
$listing = new ExtensionDiscovery(\Drupal::root());
|
||||
$files = $listing->scan('module');
|
||||
foreach ($files as $name => $file) {
|
||||
$parsed = $parser->parse($file->getPathname());
|
||||
if (!empty($parsed) && !empty($parsed['required']) && $parsed['required']) {
|
||||
$required[] = $name;
|
||||
}
|
||||
}
|
||||
|
||||
$locale = !empty($langcode) && $langcode != 'en' ? ['locale'] : [];
|
||||
|
||||
// Merge dependencies, required modules and locale into install list and
|
||||
// remove any duplicates.
|
||||
$info['install'] = array_unique(array_merge($info['install'], $required, $info['dependencies'], $locale));
|
||||
|
||||
// If the profile has a config/sync directory use that to install drupal.
|
||||
if (is_dir($profile_path . '/config/sync')) {
|
||||
$info['config_install_path'] = $profile_path . '/config/sync';
|
||||
}
|
||||
$cache[$profile][$langcode] = $info;
|
||||
}
|
||||
return $cache[$profile][$langcode];
|
||||
}
|
||||
|
||||
/**
|
||||
* Checks a module or profile requirements.
|
||||
*
|
||||
* See \Drupal\Core\Extension\InstallRequirementsInterface for more information.
|
||||
*
|
||||
* @param \Drupal\Core\Extension $extension
|
||||
* The extension to check install requirements for.
|
||||
*
|
||||
* @return array
|
||||
* The requirements.
|
||||
*
|
||||
* @internal
|
||||
*/
|
||||
function install_check_class_requirements(Extension $extension): array {
|
||||
$extension_path = $extension->getPath();
|
||||
$extension_name = $extension->getName();
|
||||
$dir = \Drupal::root() . "/$extension_path/src/Install/Requirements";
|
||||
$requirements = [];
|
||||
if (is_dir($dir)) {
|
||||
$fileSystemIterator = new FilesystemIterator($dir);
|
||||
foreach ($fileSystemIterator as $fileInfo) {
|
||||
if ($fileInfo->isFile() && $fileInfo->getExtension() === 'php') {
|
||||
$filename = $fileInfo->getFilename();
|
||||
$requirements_path = $dir . '/' . $filename;
|
||||
require_once $requirements_path;
|
||||
|
||||
$namespace = "Drupal\\$extension_name\\Install\\Requirements";
|
||||
$class_name = $namespace . '\\' . $fileInfo->getBasename('.php');
|
||||
if (class_exists($class_name) && class_implements($class_name, InstallRequirementsInterface::class)) {
|
||||
$requirements = $class_name::getRequirements();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
return $requirements;
|
||||
}
|
||||
75
web/core/includes/module.inc
Normal file
75
web/core/includes/module.inc
Normal file
@ -0,0 +1,75 @@
|
||||
<?php
|
||||
|
||||
/**
|
||||
* @file
|
||||
* API for loading and interacting with Drupal modules.
|
||||
*/
|
||||
|
||||
/**
|
||||
* 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): void {
|
||||
$extension_config = \Drupal::configFactory()->getEditable('core.extension');
|
||||
if ($extension_config->get("module.$module") !== NULL) {
|
||||
// Pre-cast the $weight to an integer so that we can save this without using
|
||||
// schema. This is a performance improvement for module installation.
|
||||
$extension_config
|
||||
->set("module.$module", (int) $weight)
|
||||
->set('module', module_config_sort($extension_config->get('module')))
|
||||
->save(TRUE);
|
||||
|
||||
// Prepare the new module list, sorted by weight, including filenames.
|
||||
// @see \Drupal\Core\Extension\ModuleInstaller::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, $extension_config->get('module')));
|
||||
$module_filenames = [];
|
||||
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 = [];
|
||||
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;
|
||||
}
|
||||
1566
web/core/includes/theme.inc
Normal file
1566
web/core/includes/theme.inc
Normal file
File diff suppressed because it is too large
Load Diff
141
web/core/includes/theme.maintenance.inc
Normal file
141
web/core/includes/theme.maintenance.inc
Normal file
@ -0,0 +1,141 @@
|
||||
<?php
|
||||
|
||||
/**
|
||||
* @file
|
||||
* Theming for maintenance pages.
|
||||
*/
|
||||
|
||||
use Drupal\Core\Installer\InstallerKernel;
|
||||
use Drupal\Core\Site\Settings;
|
||||
|
||||
/**
|
||||
* Sets up the theming system for maintenance page.
|
||||
*
|
||||
* Used for site installs, updates and when the site is in maintenance mode.
|
||||
* It also applies when the database is unavailable or bootstrap was not
|
||||
* complete. Claro is always used for the initial install and update
|
||||
* operations, but this can be overridden by setting a "maintenance_theme" key
|
||||
* in the $settings variable in settings.php.
|
||||
*/
|
||||
function _drupal_maintenance_theme(): void {
|
||||
// If the theme is already set, assume the others are set too, and do nothing.
|
||||
if (\Drupal::theme()->hasActiveTheme()) {
|
||||
return;
|
||||
}
|
||||
|
||||
require_once __DIR__ . '/theme.inc';
|
||||
require_once __DIR__ . '/common.inc';
|
||||
require_once __DIR__ . '/module.inc';
|
||||
|
||||
// Install and update pages are treated differently to prevent theming
|
||||
// overrides.
|
||||
if (defined('MAINTENANCE_MODE') && (MAINTENANCE_MODE == 'install' || MAINTENANCE_MODE == 'update')) {
|
||||
if (InstallerKernel::installationAttempted()) {
|
||||
$custom_theme = $GLOBALS['install_state']['theme'];
|
||||
}
|
||||
else {
|
||||
$custom_theme = Settings::get('maintenance_theme', 'claro');
|
||||
}
|
||||
}
|
||||
else {
|
||||
// Use the maintenance theme if specified, otherwise attempt to use the
|
||||
// default site theme.
|
||||
try {
|
||||
$custom_theme = Settings::get('maintenance_theme', '');
|
||||
if (!$custom_theme) {
|
||||
$config = \Drupal::config('system.theme');
|
||||
$custom_theme = $config->get('default');
|
||||
}
|
||||
}
|
||||
catch (\Exception) {
|
||||
// Whatever went wrong (often a database connection problem), we are
|
||||
// about to fall back to a sensible theme so there is no need for special
|
||||
// handling.
|
||||
}
|
||||
if (!$custom_theme) {
|
||||
// We have been unable to identify the configured theme, so fall back to
|
||||
// a safe default. Claro is reasonably user friendly and fairly generic.
|
||||
$custom_theme = 'claro';
|
||||
}
|
||||
}
|
||||
|
||||
$themes = \Drupal::service('theme_handler')->listInfo();
|
||||
|
||||
// If no themes are installed yet, or if the requested custom theme is not
|
||||
// installed, retrieve all available themes.
|
||||
/** @var \Drupal\Core\Theme\ThemeInitialization $theme_init */
|
||||
$theme_init = \Drupal::service('theme.initialization');
|
||||
$theme_handler = \Drupal::service('theme_handler');
|
||||
if (empty($themes) || !isset($themes[$custom_theme])) {
|
||||
$themes = \Drupal::service('extension.list.theme')->getList();
|
||||
$theme_handler->addTheme($themes[$custom_theme]);
|
||||
}
|
||||
|
||||
// \Drupal\Core\Extension\ThemeHandlerInterface::listInfo() triggers a
|
||||
// \Drupal\Core\Extension\ModuleHandler::alter() in maintenance mode, but we
|
||||
// can't let themes alter the .info.yml data until we know a theme's base
|
||||
// themes. So don't set active theme until after
|
||||
// \Drupal\Core\Extension\ThemeHandlerInterface::listInfo() builds its cache.
|
||||
$theme = $custom_theme;
|
||||
|
||||
// Find all our ancestor themes and put them in an array.
|
||||
// @todo This is just a workaround. Find a better way how to handle themes
|
||||
// on maintenance pages, see https://www.drupal.org/node/2322619.
|
||||
// This code is basically a duplicate of
|
||||
// \Drupal\Core\Theme\ThemeInitialization::getActiveThemeByName.
|
||||
$base_themes = [];
|
||||
$ancestor = $theme;
|
||||
while ($ancestor && isset($themes[$ancestor]->base_theme)) {
|
||||
$base_themes[] = $themes[$themes[$ancestor]->base_theme];
|
||||
$ancestor = $themes[$ancestor]->base_theme;
|
||||
if ($ancestor) {
|
||||
// Ensure that the base theme is added and installed.
|
||||
$theme_handler->addTheme($themes[$ancestor]);
|
||||
}
|
||||
}
|
||||
\Drupal::theme()->setActiveTheme($theme_init->getActiveTheme($themes[$custom_theme], $base_themes));
|
||||
// Prime the theme registry.
|
||||
Drupal::service('theme.registry');
|
||||
}
|
||||
|
||||
/**
|
||||
* Prepares variables for authorize.php operation report templates.
|
||||
*
|
||||
* This report displays the results of an operation run via authorize.php.
|
||||
*
|
||||
* Default template: authorize-report.html.twig.
|
||||
*
|
||||
* @param array $variables
|
||||
* An associative array containing:
|
||||
* - messages: An array of result messages.
|
||||
*
|
||||
* @deprecated in drupal:11.2.0 and is removed from drupal:12.0.0. There is no
|
||||
* replacement. Use composer to manage the code for your site.
|
||||
*
|
||||
* @see https://www.drupal.org/node/3522119
|
||||
*/
|
||||
function template_preprocess_authorize_report(&$variables): void {
|
||||
@trigger_error(__FUNCTION__ . '() is deprecated in drupal:11.2.0 and is removed from drupal:12.0.0. There is no replacement. Use composer to manage the code for your site. See https://www.drupal.org/node/3522119', E_USER_DEPRECATED);
|
||||
$messages = [];
|
||||
if (!empty($variables['messages'])) {
|
||||
foreach ($variables['messages'] as $heading => $logs) {
|
||||
$items = [];
|
||||
foreach ($logs as $number => $log_message) {
|
||||
if ($number === '#abort') {
|
||||
continue;
|
||||
}
|
||||
$class = 'authorize-results__' . ($log_message['success'] ? 'success' : 'failure');
|
||||
$items[] = [
|
||||
'#wrapper_attributes' => ['class' => [$class]],
|
||||
'#markup' => $log_message['message'],
|
||||
];
|
||||
}
|
||||
$messages[] = [
|
||||
'#theme' => 'item_list',
|
||||
'#items' => $items,
|
||||
'#title' => $heading,
|
||||
];
|
||||
}
|
||||
}
|
||||
$variables['messages'] = $messages;
|
||||
}
|
||||
709
web/core/includes/update.inc
Normal file
709
web/core/includes/update.inc
Normal file
@ -0,0 +1,709 @@
|
||||
<?php
|
||||
|
||||
/**
|
||||
* @file
|
||||
* Drupal database update API.
|
||||
*
|
||||
* This file contains functions to perform database updates for a Drupal
|
||||
* installation. It is included and used extensively by update.php.
|
||||
*/
|
||||
|
||||
use Drupal\Component\Graph\Graph;
|
||||
use Drupal\Core\Extension\Exception\UnknownExtensionException;
|
||||
use Drupal\Core\Extension\Requirement\RequirementSeverity;
|
||||
use Drupal\Core\Utility\Error;
|
||||
use Drupal\Core\Update\EquivalentUpdate;
|
||||
|
||||
/**
|
||||
* Returns whether the minimum schema requirement has been satisfied.
|
||||
*
|
||||
* @return array
|
||||
* A requirements info array.
|
||||
*/
|
||||
function update_system_schema_requirements(): array {
|
||||
$requirements = [];
|
||||
|
||||
$system_schema = \Drupal::service('update.update_hook_registry')->getInstalledVersion('system');
|
||||
|
||||
$requirements['minimum schema']['title'] = 'Minimum schema version';
|
||||
if ($system_schema >= \Drupal::CORE_MINIMUM_SCHEMA_VERSION) {
|
||||
$requirements['minimum schema'] += [
|
||||
'value' => 'The installed schema version meets the minimum.',
|
||||
'description' => 'Schema version: ' . $system_schema,
|
||||
];
|
||||
}
|
||||
else {
|
||||
$requirements['minimum schema'] += [
|
||||
'value' => 'The installed schema version does not meet the minimum.',
|
||||
'severity' => RequirementSeverity::Error,
|
||||
'description' => 'Your system schema version is ' . $system_schema . '. Updating directly from a schema version prior to 8000 is not supported. You must upgrade your site to Drupal 8 first, see https://www.drupal.org/docs/8/upgrade.',
|
||||
];
|
||||
}
|
||||
|
||||
return $requirements;
|
||||
}
|
||||
|
||||
/**
|
||||
* Checks update requirements and reports errors and (optionally) warnings.
|
||||
*/
|
||||
function update_check_requirements() {
|
||||
// Because this is one of the earliest points in the update process,
|
||||
// detect and fix missing schema versions for modules here to ensure
|
||||
// it runs on all update code paths.
|
||||
_update_fix_missing_schema();
|
||||
|
||||
// Check requirements of all loaded modules.
|
||||
$requirements = array_merge(
|
||||
\Drupal::moduleHandler()->invokeAll('requirements', ['update']),
|
||||
\Drupal::moduleHandler()->invokeAll('update_requirements')
|
||||
);
|
||||
\Drupal::moduleHandler()->alter('requirements', $requirements);
|
||||
\Drupal::moduleHandler()->alter('update_requirements', $requirements);
|
||||
$requirements += update_system_schema_requirements();
|
||||
return $requirements;
|
||||
}
|
||||
|
||||
/**
|
||||
* Helper to detect and fix 'missing' schema information.
|
||||
*
|
||||
* Repairs the case where a module has no schema version recorded.
|
||||
* This has to be done prior to updates being run, otherwise the update
|
||||
* system would detect and attempt to run all historical updates for a
|
||||
* module.
|
||||
*
|
||||
* @todo remove in a major version after
|
||||
* https://www.drupal.org/project/drupal/issues/3130037 has been fixed.
|
||||
*/
|
||||
function _update_fix_missing_schema(): void {
|
||||
/** @var \Drupal\Core\Update\UpdateHookRegistry $update_registry */
|
||||
$update_registry = \Drupal::service('update.update_hook_registry');
|
||||
$versions = $update_registry->getAllInstalledVersions();
|
||||
$module_handler = \Drupal::moduleHandler();
|
||||
$enabled_modules = $module_handler->getModuleList();
|
||||
|
||||
foreach (array_keys($enabled_modules) as $module) {
|
||||
// All modules should have a recorded schema version, but when they
|
||||
// don't, detect and fix the problem.
|
||||
if (!isset($versions[$module])) {
|
||||
// Ensure the .install file is loaded.
|
||||
$module_handler->loadInclude($module, 'install');
|
||||
$all_updates = $update_registry->getAvailableUpdates($module);
|
||||
// If the schema version of a module hasn't been recorded, we cannot
|
||||
// know the actual schema version a module is at, because
|
||||
// no updates will ever have been run on the site and it was not set
|
||||
// correctly when the module was installed, so instead set it to
|
||||
// the same as the last update. This means that updates will proceed
|
||||
// again the next time the module is updated and a new update is
|
||||
// added. Updates added in between the module being installed and the
|
||||
// schema version being fixed here (if any have been added) will never
|
||||
// be run, but we have no way to identify which updates these are.
|
||||
if ($all_updates) {
|
||||
$last_update = max($all_updates);
|
||||
}
|
||||
else {
|
||||
$last_update = \Drupal::CORE_MINIMUM_SCHEMA_VERSION;
|
||||
}
|
||||
// If the module implements hook_update_last_removed() use the
|
||||
// value of that if it's higher than the schema versions found so
|
||||
// far.
|
||||
$function = $module . '_update_last_removed';
|
||||
if (function_exists($function) && ($last_removed = $function())) {
|
||||
$last_update = max($last_update, $last_removed);
|
||||
}
|
||||
$update_registry->setInstalledVersion($module, $last_update);
|
||||
$args = ['%module' => $module, '%last_update_hook' => $module . '_update_' . $last_update . '()'];
|
||||
\Drupal::messenger()->addWarning(t('Schema information for module %module was missing from the database. You should manually review the module updates and your database to check if any updates have been skipped up to, and including, %last_update_hook.', $args));
|
||||
\Drupal::logger('update')->warning('Schema information for module %module was missing from the database. You should manually review the module updates and your database to check if any updates have been skipped up to, and including, %last_update_hook.', $args);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Implements callback_batch_operation().
|
||||
*
|
||||
* Performs one update and stores the results for display on the results page.
|
||||
*
|
||||
* If an update function completes successfully, it should return a message
|
||||
* as a string indicating success, for example:
|
||||
* @code
|
||||
* return t('New index added successfully.');
|
||||
* @endcode
|
||||
*
|
||||
* Alternatively, it may return nothing. In that case, no message
|
||||
* will be displayed at all.
|
||||
*
|
||||
* If it fails for whatever reason, it should throw an instance of
|
||||
* Drupal\Core\Utility\UpdateException with an appropriate error message, for
|
||||
* example:
|
||||
* @code
|
||||
* use Drupal\Core\Utility\UpdateException;
|
||||
* throw new UpdateException('Description of what went wrong');
|
||||
* @endcode
|
||||
*
|
||||
* If an exception is thrown, the current update and all updates that depend on
|
||||
* it will be aborted. The schema version will not be updated in this case, and
|
||||
* all the aborted updates will continue to appear on update.php as updates
|
||||
* that have not yet been run.
|
||||
*
|
||||
* If an update function needs to be re-run as part of a batch process, it
|
||||
* should accept the $sandbox array by reference as its first parameter
|
||||
* and set the #finished property to the percentage completed that it is, as a
|
||||
* fraction of 1.
|
||||
*
|
||||
* @param string $module
|
||||
* The module whose update will be run.
|
||||
* @param string $number
|
||||
* The update number to run.
|
||||
* @param array $dependency_map
|
||||
* An array whose keys are the names of all update functions that will be
|
||||
* performed during this batch process, and whose values are arrays of other
|
||||
* update functions that each one depends on.
|
||||
* @param array $context
|
||||
* The batch context array.
|
||||
*
|
||||
* @see update_resolve_dependencies()
|
||||
*/
|
||||
function update_do_one($module, $number, $dependency_map, &$context): void {
|
||||
$function = $module . '_update_' . $number;
|
||||
|
||||
// If this update was aborted in a previous step, or has a dependency that
|
||||
// was aborted in a previous step, go no further.
|
||||
if (!empty($context['results']['#abort']) && array_intersect($context['results']['#abort'], array_merge($dependency_map, [$function]))) {
|
||||
return;
|
||||
}
|
||||
|
||||
$ret = [];
|
||||
$equivalent_update = \Drupal::service('update.update_hook_registry')->getEquivalentUpdate($module, $number);
|
||||
if ($equivalent_update instanceof EquivalentUpdate) {
|
||||
$ret['results']['query'] = $equivalent_update->toSkipMessage();
|
||||
$ret['results']['success'] = TRUE;
|
||||
$context['sandbox']['#finished'] = TRUE;
|
||||
}
|
||||
elseif (function_exists($function)) {
|
||||
try {
|
||||
$ret['results']['query'] = $function($context['sandbox']);
|
||||
$ret['results']['success'] = TRUE;
|
||||
}
|
||||
// @todo We may want to do different error handling for different
|
||||
// exception types, but for now we'll just log the exception and
|
||||
// return the message for printing.
|
||||
// @see https://www.drupal.org/node/2564311
|
||||
catch (Exception $e) {
|
||||
$variables = Error::decodeException($e);
|
||||
\Drupal::logger('update')->error(Error::DEFAULT_ERROR_MESSAGE, $variables);
|
||||
|
||||
unset($variables['backtrace'], $variables['exception'], $variables['severity_level']);
|
||||
// phpcs:ignore Drupal.Semantics.FunctionT.NotLiteralString
|
||||
$ret['#abort'] = ['success' => FALSE, 'query' => t(Error::DEFAULT_ERROR_MESSAGE, $variables)];
|
||||
}
|
||||
}
|
||||
|
||||
if (isset($context['sandbox']['#finished'])) {
|
||||
$context['finished'] = $context['sandbox']['#finished'];
|
||||
unset($context['sandbox']['#finished']);
|
||||
}
|
||||
|
||||
if (!isset($context['results'][$module])) {
|
||||
$context['results'][$module] = [];
|
||||
}
|
||||
if (!isset($context['results'][$module][$number])) {
|
||||
$context['results'][$module][$number] = [];
|
||||
}
|
||||
$context['results'][$module][$number] = array_merge($context['results'][$module][$number], $ret);
|
||||
|
||||
if (!empty($ret['#abort'])) {
|
||||
// Record this function in the list of updates that were aborted.
|
||||
$context['results']['#abort'][] = $function;
|
||||
}
|
||||
|
||||
// Record the schema update if it was completed successfully.
|
||||
if ($context['finished'] == 1 && empty($ret['#abort'])) {
|
||||
\Drupal::service('update.update_hook_registry')->setInstalledVersion($module, $number);
|
||||
}
|
||||
|
||||
$context['message'] = t('Updating @module', ['@module' => $module]);
|
||||
}
|
||||
|
||||
/**
|
||||
* Executes a single hook_post_update_NAME().
|
||||
*
|
||||
* @param string $function
|
||||
* The function name, that should be executed.
|
||||
* @param array $context
|
||||
* The batch context array.
|
||||
*/
|
||||
function update_invoke_post_update($function, &$context): void {
|
||||
$ret = [];
|
||||
|
||||
// If this update was aborted in a previous step, or has a dependency that was
|
||||
// aborted in a previous step, go no further.
|
||||
if (!empty($context['results']['#abort'])) {
|
||||
return;
|
||||
}
|
||||
|
||||
// Ensure extension post update code is loaded.
|
||||
[$extension, $name] = explode('_post_update_', $function, 2);
|
||||
\Drupal::service('update.post_update_registry')->getUpdateFunctions($extension);
|
||||
|
||||
if (function_exists($function)) {
|
||||
try {
|
||||
$ret['results']['query'] = $function($context['sandbox']);
|
||||
$ret['results']['success'] = TRUE;
|
||||
|
||||
if (!isset($context['sandbox']['#finished']) || (isset($context['sandbox']['#finished']) && $context['sandbox']['#finished'] >= 1)) {
|
||||
\Drupal::service('update.post_update_registry')->registerInvokedUpdates([$function]);
|
||||
}
|
||||
}
|
||||
// @todo We may want to do different error handling for different exception
|
||||
// types, but for now we'll just log the exception and return the message
|
||||
// for printing.
|
||||
// @see https://www.drupal.org/node/2564311
|
||||
catch (Exception $e) {
|
||||
$variables = Error::decodeException($e);
|
||||
\Drupal::logger('update')->error(Error::DEFAULT_ERROR_MESSAGE, $variables);
|
||||
unset($variables['backtrace'], $variables['exception'], $variables['severity_level']);
|
||||
$ret['#abort'] = [
|
||||
'success' => FALSE,
|
||||
// phpcs:ignore Drupal.Semantics.FunctionT.NotLiteralString
|
||||
'query' => t(Error::DEFAULT_ERROR_MESSAGE, $variables),
|
||||
];
|
||||
}
|
||||
}
|
||||
|
||||
if (isset($context['sandbox']['#finished'])) {
|
||||
$context['finished'] = $context['sandbox']['#finished'];
|
||||
unset($context['sandbox']['#finished']);
|
||||
}
|
||||
if (!isset($context['results'][$extension][$name])) {
|
||||
$context['results'][$extension][$name] = [];
|
||||
}
|
||||
$context['results'][$extension][$name] = array_merge($context['results'][$extension][$name], $ret);
|
||||
|
||||
if (!empty($ret['#abort'])) {
|
||||
// Record this function in the list of updates that were aborted.
|
||||
$context['results']['#abort'][] = $function;
|
||||
}
|
||||
|
||||
$context['message'] = t('Post updating @extension', ['@extension' => $extension]);
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns a list of all the pending database updates.
|
||||
*
|
||||
* @return array
|
||||
* An associative array keyed by module name which contains all information
|
||||
* about database updates that need to be run, and any updates that are not
|
||||
* going to proceed due to missing requirements. The system module will
|
||||
* always be listed first.
|
||||
*
|
||||
* The subarray for each module can contain the following keys:
|
||||
* - start: The starting update that is to be processed. If this does not
|
||||
* exist then do not process any updates for this module as there are
|
||||
* other requirements that need to be resolved.
|
||||
* - warning: Any warnings about why this module can not be updated.
|
||||
* - pending: An array of all the pending updates for the module including
|
||||
* the update number and the description from source code comment for
|
||||
* each update function. This array is keyed by the update number.
|
||||
*/
|
||||
function update_get_update_list(): array {
|
||||
// Make sure that the system module is first in the list of updates.
|
||||
$ret = ['system' => []];
|
||||
|
||||
/** @var \Drupal\Core\Update\UpdateHookRegistry $update_registry */
|
||||
$update_registry = \Drupal::service('update.update_hook_registry');
|
||||
$modules = $update_registry->getAllInstalledVersions();
|
||||
/** @var \Drupal\Core\Extension\ExtensionList $extension_list */
|
||||
$extension_list = \Drupal::service('extension.list.module');
|
||||
/** @var array $installed_module_info */
|
||||
$installed_module_info = $extension_list->getAllInstalledInfo();
|
||||
foreach ($modules as $module => $schema_version) {
|
||||
// Skip uninstalled and incompatible modules.
|
||||
try {
|
||||
if ($schema_version == $update_registry::SCHEMA_UNINSTALLED || $extension_list->checkIncompatibility($module)) {
|
||||
continue;
|
||||
}
|
||||
}
|
||||
// It is possible that the system schema has orphaned entries, so the
|
||||
// incompatibility checking might throw an exception.
|
||||
catch (UnknownExtensionException) {
|
||||
$args = [
|
||||
'%name' => $module,
|
||||
':url' => 'https://www.drupal.org/node/3137656',
|
||||
];
|
||||
\Drupal::messenger()->addWarning(t('Module %name has an entry in the system.schema key/value storage, but is missing from your site. <a href=":url">More information about this error</a>.', $args));
|
||||
\Drupal::logger('system')->notice('Module %name has an entry in the system.schema key/value storage, but is missing from your site. <a href=":url">More information about this error</a>.', $args);
|
||||
continue;
|
||||
}
|
||||
// There might be orphaned entries for modules that are in the filesystem
|
||||
// but not installed. Also skip those, but warn site admins about it.
|
||||
if (empty($installed_module_info[$module])) {
|
||||
$args = [
|
||||
'%name' => $module,
|
||||
':url' => 'https://www.drupal.org/node/3137656',
|
||||
];
|
||||
\Drupal::messenger()->addWarning(t('Module %name has an entry in the system.schema key/value storage, but is not installed. <a href=":url">More information about this error</a>.', $args));
|
||||
\Drupal::logger('system')->notice('Module %name has an entry in the system.schema key/value storage, but is not installed. <a href=":url">More information about this error</a>.', $args);
|
||||
continue;
|
||||
}
|
||||
|
||||
// Display a requirements error if the user somehow has a schema version
|
||||
// from the previous Drupal major version.
|
||||
if ($schema_version < \Drupal::CORE_MINIMUM_SCHEMA_VERSION) {
|
||||
$ret[$module]['warning'] = '<em>' . $module . '</em> module cannot be updated. Its schema version is ' . $schema_version . ', which is from an earlier major release of Drupal. See the <a href="https://www.drupal.org/docs/upgrading-drupal">Upgrading Drupal guide</a>.';
|
||||
continue;
|
||||
}
|
||||
// Otherwise, get the list of updates defined by this module.
|
||||
$updates = $update_registry->getAvailableUpdates($module);
|
||||
if ($updates) {
|
||||
foreach ($updates as $update) {
|
||||
if ($update == \Drupal::CORE_MINIMUM_SCHEMA_VERSION) {
|
||||
$ret[$module]['warning'] = '<em>' . $module . '</em> module cannot be updated. It contains an update numbered as ' . \Drupal::CORE_MINIMUM_SCHEMA_VERSION . ' which is reserved for the earliest installation of a module in Drupal ' . \Drupal::CORE_COMPATIBILITY . ', before any updates. In order to update <em>' . $module . '</em> module, you will need to download a version of the module with valid updates.';
|
||||
continue 2;
|
||||
}
|
||||
if ($update > $schema_version) {
|
||||
// The description for an update comes from its Doxygen.
|
||||
$func = new ReflectionFunction($module . '_update_' . $update);
|
||||
$patterns = [
|
||||
'/^\s*[\/*]*/',
|
||||
'/(\n\s*\**)(.*)/',
|
||||
'/\/$/',
|
||||
'/^\s*/',
|
||||
];
|
||||
$replacements = ['', '$2', '', ''];
|
||||
$description = preg_replace($patterns, $replacements, $func->getDocComment());
|
||||
$ret[$module]['pending'][$update] = "$update - $description";
|
||||
if (!isset($ret[$module]['start'])) {
|
||||
$ret[$module]['start'] = $update;
|
||||
}
|
||||
}
|
||||
}
|
||||
if (!isset($ret[$module]['start']) && isset($ret[$module]['pending'])) {
|
||||
$ret[$module]['start'] = $schema_version;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (empty($ret['system'])) {
|
||||
unset($ret['system']);
|
||||
}
|
||||
return $ret;
|
||||
}
|
||||
|
||||
/**
|
||||
* Resolves dependencies in a set of module updates, and orders them correctly.
|
||||
*
|
||||
* This function receives a list of requested module updates and determines an
|
||||
* appropriate order to run them in such that all update dependencies are met.
|
||||
* Any updates whose dependencies cannot be met are included in the returned
|
||||
* array but have the key 'allowed' set to FALSE; the calling function should
|
||||
* take responsibility for ensuring that these updates are ultimately not
|
||||
* performed.
|
||||
*
|
||||
* In addition, the returned array also includes detailed information about the
|
||||
* dependency chain for each update, as provided by the depth-first search
|
||||
* algorithm in Drupal\Component\Graph\Graph::searchAndSort().
|
||||
*
|
||||
* @param array $starting_updates
|
||||
* An array whose keys contain the names of modules with updates to be run
|
||||
* and whose values contain the number of the first requested update for that
|
||||
* module.
|
||||
*
|
||||
* @return array
|
||||
* An array whose keys are the names of all update functions within the
|
||||
* provided modules that would need to be run in order to fulfill the
|
||||
* request, arranged in the order in which the update functions should be
|
||||
* run. (This includes the provided starting update for each module and all
|
||||
* subsequent updates that are available.) The values are themselves arrays
|
||||
* containing all the keys provided by the
|
||||
* Drupal\Component\Graph\Graph::searchAndSort() algorithm, which encode
|
||||
* detailed information about the dependency chain for this update function
|
||||
* (for example: 'paths', 'reverse_paths', 'weight', and 'component'), as
|
||||
* well as the following additional keys:
|
||||
* - 'allowed': A boolean which is TRUE when the update function's
|
||||
* dependencies are met, and FALSE otherwise. Calling functions should
|
||||
* inspect this value before running the update.
|
||||
* - 'missing_dependencies': An array containing the names of any other
|
||||
* update functions that are required by this one but that are unavailable
|
||||
* to be run. This array will be empty when 'allowed' is TRUE.
|
||||
* - 'module': The name of the module that this update function belongs to.
|
||||
* - 'number': The number of this update function within that module.
|
||||
*
|
||||
* @see \Drupal\Component\Graph\Graph::searchAndSort()
|
||||
*/
|
||||
function update_resolve_dependencies($starting_updates) {
|
||||
// Obtain a dependency graph for the requested update functions.
|
||||
$update_functions = update_get_update_function_list($starting_updates);
|
||||
$graph = update_build_dependency_graph($update_functions);
|
||||
|
||||
// Perform the depth-first search and sort on the results.
|
||||
$graph_object = new Graph($graph);
|
||||
$graph = $graph_object->searchAndSort();
|
||||
uasort($graph, ['Drupal\Component\Utility\SortArray', 'sortByWeightElement']);
|
||||
|
||||
foreach ($graph as $function => &$data) {
|
||||
$module = $data['module'];
|
||||
$number = $data['number'];
|
||||
// If the update function is missing and has not yet been performed, mark
|
||||
// it and everything that ultimately depends on it as disallowed.
|
||||
if (update_is_missing($module, $number, $update_functions) && !update_already_performed($module, $number)) {
|
||||
$data['allowed'] = FALSE;
|
||||
foreach (array_keys($data['paths']) as $dependent) {
|
||||
$graph[$dependent]['allowed'] = FALSE;
|
||||
$graph[$dependent]['missing_dependencies'][] = $function;
|
||||
}
|
||||
}
|
||||
elseif (!isset($data['allowed'])) {
|
||||
$data['allowed'] = TRUE;
|
||||
$data['missing_dependencies'] = [];
|
||||
}
|
||||
// Now that we have finished processing this function, remove it from the
|
||||
// graph if it was not part of the original list. This ensures that we
|
||||
// never try to run any updates that were not specifically requested.
|
||||
if (!isset($update_functions[$module][$number])) {
|
||||
unset($graph[$function]);
|
||||
}
|
||||
}
|
||||
|
||||
return $graph;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns an organized list of update functions for a set of modules.
|
||||
*
|
||||
* @param array $starting_updates
|
||||
* An array whose keys contain the names of modules and whose values contain
|
||||
* the number of the first requested update for that module.
|
||||
*
|
||||
* @return array
|
||||
* An array containing all the update functions that should be run for each
|
||||
* module, including the provided starting update and all subsequent updates
|
||||
* that are available. The keys of the array contain the module names, and
|
||||
* each value is an ordered array of update functions, keyed by the update
|
||||
* number.
|
||||
*
|
||||
* @see update_resolve_dependencies()
|
||||
*/
|
||||
function update_get_update_function_list($starting_updates): array {
|
||||
// Go through each module and find all updates that we need (including the
|
||||
// first update that was requested and any updates that run after it).
|
||||
$update_functions = [];
|
||||
/** @var \Drupal\Core\Update\UpdateHookRegistry $update_registry */
|
||||
$update_registry = \Drupal::service('update.update_hook_registry');
|
||||
foreach ($starting_updates as $module => $version) {
|
||||
$update_functions[$module] = [];
|
||||
$updates = $update_registry->getAvailableUpdates($module);
|
||||
if ($updates) {
|
||||
$max_version = max($updates);
|
||||
if ($version <= $max_version) {
|
||||
foreach ($updates as $update) {
|
||||
if ($update >= $version) {
|
||||
$update_functions[$module][$update] = $module . '_update_' . $update;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
return $update_functions;
|
||||
}
|
||||
|
||||
/**
|
||||
* Constructs a graph which encodes the dependencies between module updates.
|
||||
*
|
||||
* This function returns an associative array which contains a "directed graph"
|
||||
* representation of the dependencies between a provided list of update
|
||||
* functions, as well as any outside update functions that they directly depend
|
||||
* on but that were not in the provided list. The vertices of the graph
|
||||
* represent the update functions themselves, and each edge represents a
|
||||
* requirement that the first update function needs to run before the second.
|
||||
* For example, consider this graph:
|
||||
*
|
||||
* system_update_8001 ---> system_update_8002 ---> system_update_8003
|
||||
*
|
||||
* Visually, this indicates that system_update_8001() must run before
|
||||
* system_update_8002(), which in turn must run before system_update_8003().
|
||||
*
|
||||
* The function takes into account standard dependencies within each module, as
|
||||
* shown above (i.e., the fact that each module's updates must run in numerical
|
||||
* order), but also finds any cross-module dependencies that are defined by
|
||||
* modules which implement hook_update_dependencies(), and builds them into the
|
||||
* graph as well.
|
||||
*
|
||||
* @param array $update_functions
|
||||
* An organized array of update functions, in the format returned by
|
||||
* update_get_update_function_list().
|
||||
*
|
||||
* @return array
|
||||
* A multidimensional array representing the dependency graph, suitable for
|
||||
* passing in to Drupal\Component\Graph\Graph::searchAndSort(), but with extra
|
||||
* information about each update function also included. Each array key
|
||||
* contains the name of an update function, including all update functions
|
||||
* from the provided list as well as any outside update functions which they
|
||||
* directly depend on. Each value is an associative array containing the
|
||||
* following keys:
|
||||
* - 'edges': A representation of any other update functions that immediately
|
||||
* depend on this one. See Drupal\Component\Graph\Graph::searchAndSort() for
|
||||
* more details on the format.
|
||||
* - 'module': The name of the module that this update function belongs to.
|
||||
* - 'number': The number of this update function within that module.
|
||||
*
|
||||
* @see \Drupal\Component\Graph\Graph::searchAndSort()
|
||||
* @see update_resolve_dependencies()
|
||||
*/
|
||||
function update_build_dependency_graph($update_functions): array {
|
||||
// Initialize an array that will define a directed graph representing the
|
||||
// dependencies between update functions.
|
||||
$graph = [];
|
||||
|
||||
// Go through each update function and build an initial list of dependencies.
|
||||
foreach ($update_functions as $module => $functions) {
|
||||
$previous_function = NULL;
|
||||
foreach ($functions as $number => $function) {
|
||||
// Add an edge to the directed graph representing the fact that each
|
||||
// update function in a given module must run after the update that
|
||||
// numerically precedes it.
|
||||
if ($previous_function) {
|
||||
$graph[$previous_function]['edges'][$function] = TRUE;
|
||||
}
|
||||
$previous_function = $function;
|
||||
|
||||
// Define the module and update number associated with this function.
|
||||
$graph[$function]['module'] = $module;
|
||||
$graph[$function]['number'] = $number;
|
||||
}
|
||||
}
|
||||
|
||||
// Now add any explicit update dependencies declared by modules.
|
||||
$update_dependencies = update_retrieve_dependencies();
|
||||
foreach ($graph as $function => $data) {
|
||||
if (!empty($update_dependencies[$data['module']][$data['number']])) {
|
||||
foreach ($update_dependencies[$data['module']][$data['number']] as $module => $number) {
|
||||
$dependency = $module . '_update_' . $number;
|
||||
$graph[$dependency]['edges'][$function] = TRUE;
|
||||
$graph[$dependency]['module'] = $module;
|
||||
$graph[$dependency]['number'] = $number;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return $graph;
|
||||
}
|
||||
|
||||
/**
|
||||
* Determines if a module update is missing or unavailable.
|
||||
*
|
||||
* @param string $module
|
||||
* The name of the module.
|
||||
* @param int $number
|
||||
* The number of the update within that module.
|
||||
* @param array $update_functions
|
||||
* An organized array of update functions, in the format returned by
|
||||
* update_get_update_function_list(). This should represent all module
|
||||
* updates that are requested to run at the time this function is called.
|
||||
*
|
||||
* @return bool
|
||||
* TRUE if the provided module update is not installed or is not in the
|
||||
* provided list of updates to run; FALSE otherwise.
|
||||
*/
|
||||
function update_is_missing($module, $number, $update_functions) {
|
||||
return !isset($update_functions[$module][$number]) || !function_exists($update_functions[$module][$number]);
|
||||
}
|
||||
|
||||
/**
|
||||
* Determines if a module update has already been performed.
|
||||
*
|
||||
* @param string $module
|
||||
* The name of the module.
|
||||
* @param int $number
|
||||
* The number of the update within that module.
|
||||
*
|
||||
* @return bool
|
||||
* TRUE if the database schema indicates that the update has already been
|
||||
* performed; FALSE otherwise.
|
||||
*/
|
||||
function update_already_performed($module, $number) {
|
||||
return $number <= \Drupal::service('update.update_hook_registry')->getInstalledVersion($module);
|
||||
}
|
||||
|
||||
/**
|
||||
* Invokes hook_update_dependencies() in all installed modules.
|
||||
*
|
||||
* This function is similar to \Drupal::moduleHandler()->invokeAll(), with the
|
||||
* main difference that it does not require that a module be enabled to invoke
|
||||
* its hook, only that it be installed. This allows the update system to
|
||||
* properly perform updates even on modules that are currently disabled.
|
||||
*
|
||||
* @return array
|
||||
* An array of return values obtained by merging the results of the
|
||||
* hook_update_dependencies() implementations in all installed modules.
|
||||
*
|
||||
* @see \Drupal\Core\Extension\ModuleHandlerInterface::invokeAll()
|
||||
* @see hook_update_dependencies()
|
||||
*/
|
||||
function update_retrieve_dependencies(): array {
|
||||
$return = [];
|
||||
/** @var \Drupal\Core\Extension\ModuleExtensionList */
|
||||
$extension_list = \Drupal::service('extension.list.module');
|
||||
/** @var \Drupal\Core\Update\UpdateHookRegistry $update_registry */
|
||||
$update_registry = \Drupal::service('update.update_hook_registry');
|
||||
// Get a list of installed modules, arranged so that we invoke their hooks in
|
||||
// the same order that \Drupal::moduleHandler()->invokeAll() does.
|
||||
foreach ($update_registry->getAllInstalledVersions() as $module => $schema) {
|
||||
// Skip modules that are entirely missing from the filesystem here, since
|
||||
// loading .install file will call trigger_error() if invoked on a module
|
||||
// that doesn't exist. There's no way to catch() that, so avoid it entirely.
|
||||
// This can happen when there are orphaned entries in the system.schema k/v
|
||||
// store for modules that have been removed from a site without first being
|
||||
// cleanly uninstalled. We don't care here if the module has been installed
|
||||
// or not, since we'll filter those out in update_get_update_list().
|
||||
if ($schema == $update_registry::SCHEMA_UNINSTALLED || !$extension_list->exists($module)) {
|
||||
// Nothing to upgrade.
|
||||
continue;
|
||||
}
|
||||
$function = $module . '_update_dependencies';
|
||||
// Ensure install file is loaded.
|
||||
\Drupal::moduleHandler()->loadInclude($module, 'install');
|
||||
if (function_exists($function)) {
|
||||
$updated_dependencies = $function();
|
||||
// Each implementation of hook_update_dependencies() returns a
|
||||
// multidimensional, associative array containing some keys that
|
||||
// represent module names (which are strings) and other keys that
|
||||
// represent update function numbers (which are integers). We cannot use
|
||||
// array_merge_recursive() to properly merge these results, since it
|
||||
// treats strings and integers differently. Therefore, we have to
|
||||
// explicitly loop through the expected array structure here and perform
|
||||
// the merge manually.
|
||||
if (isset($updated_dependencies) && is_array($updated_dependencies)) {
|
||||
foreach ($updated_dependencies as $module_name => $module_data) {
|
||||
foreach ($module_data as $update_version => $update_data) {
|
||||
foreach ($update_data as $module_dependency => $update_dependency) {
|
||||
// If there are redundant dependencies declared for the same
|
||||
// update function (so that it is declared to depend on more than
|
||||
// one update from a particular module), record the dependency on
|
||||
// the highest numbered update here, since that automatically
|
||||
// implies the previous ones. For example, if one module's
|
||||
// implementation of hook_update_dependencies() required this
|
||||
// ordering:
|
||||
//
|
||||
// system_update_8002 ---> user_update_8001
|
||||
//
|
||||
// but another module's implementation of the hook required this
|
||||
// one:
|
||||
//
|
||||
// system_update_8003 ---> user_update_8001
|
||||
//
|
||||
// we record the second one, since system_update_8002() is always
|
||||
// guaranteed to run before system_update_8003() anyway (within
|
||||
// an individual module, updates are always run in numerical
|
||||
// order).
|
||||
if (!isset($return[$module_name][$update_version][$module_dependency]) || $update_dependency > $return[$module_name][$update_version][$module_dependency]) {
|
||||
$return[$module_name][$update_version][$module_dependency] = $update_dependency;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return $return;
|
||||
}
|
||||
50
web/core/includes/utility.inc
Normal file
50
web/core/includes/utility.inc
Normal file
@ -0,0 +1,50 @@
|
||||
<?php
|
||||
|
||||
/**
|
||||
* @file
|
||||
* Miscellaneous functions.
|
||||
*/
|
||||
|
||||
use Drupal\Core\DrupalKernel;
|
||||
use Symfony\Component\HttpFoundation\Request;
|
||||
|
||||
/**
|
||||
* Rebuilds all caches even when Drupal itself does not work.
|
||||
*
|
||||
* @param \Composer\Autoload\ClassLoader $class_loader
|
||||
* The class loader. Normally Composer's ClassLoader, as included by the
|
||||
* front controller, but may also be decorated.
|
||||
* @param \Symfony\Component\HttpFoundation\Request $request
|
||||
* The current request.
|
||||
*
|
||||
* @see rebuild.php
|
||||
*/
|
||||
function drupal_rebuild($class_loader, Request $request): void {
|
||||
// Remove Drupal's error and exception handlers; they rely on a working
|
||||
// service container and other subsystems and will only cause a fatal error
|
||||
// that hides the actual error.
|
||||
restore_error_handler();
|
||||
restore_exception_handler();
|
||||
|
||||
// Invalidate the container.
|
||||
// Bootstrap up to where caches exist and clear them.
|
||||
$kernel = new DrupalKernel('prod', $class_loader);
|
||||
$kernel->setSitePath(DrupalKernel::findSitePath($request));
|
||||
$kernel->invalidateContainer();
|
||||
$kernel->boot();
|
||||
$kernel->preHandle($request);
|
||||
// Ensure our request includes the session if appropriate.
|
||||
if (PHP_SAPI !== 'cli') {
|
||||
$request->setSession($kernel->getContainer()->get('session'));
|
||||
}
|
||||
|
||||
drupal_flush_all_caches($kernel);
|
||||
|
||||
// Disable recording of cached pages.
|
||||
\Drupal::service('page_cache_kill_switch')->trigger();
|
||||
|
||||
// Restore Drupal's error and exception handlers.
|
||||
// @see \Drupal\Core\DrupalKernel::boot()
|
||||
set_error_handler('_drupal_error_handler');
|
||||
set_exception_handler('_drupal_exception_handler');
|
||||
}
|
||||
Reference in New Issue
Block a user