Drupal 8.0.0 beta 12. More info: https://www.drupal.org/node/2514176
This commit is contained in:
commit
9921556621
13277 changed files with 1459781 additions and 0 deletions
518
core/includes/batch.inc
Normal file
518
core/includes/batch.inc
Normal file
|
@ -0,0 +1,518 @@
|
|||
<?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\SafeMarkup;
|
||||
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;
|
||||
|
||||
/**
|
||||
* 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->get('id'))) {
|
||||
return FALSE;
|
||||
}
|
||||
|
||||
// Retrieve the current state of the batch.
|
||||
if (!$batch) {
|
||||
$batch = \Drupal::service('batch.storage')->load($request_id);
|
||||
if (!$batch) {
|
||||
drupal_set_message(t('No active batch.'), 'error');
|
||||
return new RedirectResponse(\Drupal::url('<front>', [], ['absolute' => TRUE]));
|
||||
}
|
||||
}
|
||||
// Restore safe strings from previous batches.
|
||||
// This is safe because we are passing through the known safe values from
|
||||
// SafeMarkup::getAll(). See _batch_shutdown().
|
||||
// @todo Ensure we are not storing an excessively large string list in:
|
||||
// https://www.drupal.org/node/2295823
|
||||
if (!empty($batch['safe_strings'])) {
|
||||
SafeMarkup::setMultiple($batch['safe_strings']);
|
||||
}
|
||||
// Register database update for the end of processing.
|
||||
drupal_register_shutdown_function('_batch_shutdown');
|
||||
|
||||
$build = array();
|
||||
|
||||
// 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;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
$op = $request->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();
|
||||
break;
|
||||
|
||||
case 'do':
|
||||
// JavaScript-based progress page callback.
|
||||
return _batch_do();
|
||||
|
||||
case 'finished':
|
||||
// _batch_finished() returns a RedirectResponse.
|
||||
return _batch_finished();
|
||||
}
|
||||
|
||||
return $build;
|
||||
}
|
||||
|
||||
/**
|
||||
* 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.
|
||||
list($percentage, $message, $label) = _batch_process();
|
||||
|
||||
return new JsonResponse(array('status' => TRUE, 'percentage' => $percentage, 'message' => $message, 'label' => $label));
|
||||
}
|
||||
|
||||
/**
|
||||
* Outputs a batch processing page.
|
||||
*
|
||||
* @see _batch_process()
|
||||
*/
|
||||
function _batch_progress_page() {
|
||||
$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', array(
|
||||
'#show_messages' => FALSE,
|
||||
));
|
||||
|
||||
// Just use the content of the response.
|
||||
$fallback = $response->getContent();
|
||||
|
||||
list($fallback) = explode('<!--partial-->', $fallback);
|
||||
print $fallback;
|
||||
|
||||
// Perform actual processing.
|
||||
list($percentage, $message, $label) = _batch_process($batch);
|
||||
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();
|
||||
|
||||
$build = array(
|
||||
'#theme' => 'progress_bar',
|
||||
'#percent' => $percentage,
|
||||
'#message' => $message,
|
||||
'#label' => $label,
|
||||
'#attached' => array(
|
||||
'html_head' => array(
|
||||
array(
|
||||
array(
|
||||
// Redirect through a 'Refresh' meta tag if JavaScript is disabled.
|
||||
'#tag' => 'meta',
|
||||
'#noscript' => TRUE,
|
||||
'#attributes' => array(
|
||||
'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' => array(
|
||||
'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
|
||||
* 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;
|
||||
|
||||
// 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 = $label = '';
|
||||
// Assume a single pass operation and set the completion level to 1 by
|
||||
// default.
|
||||
$finished = 1;
|
||||
|
||||
if ($item = $queue->claimItem()) {
|
||||
list($callback, $args) = $item->data;
|
||||
|
||||
// Build the 'context' array and execute the function call.
|
||||
$batch_context = array(
|
||||
'sandbox' => &$current_set['sandbox'],
|
||||
'results' => &$current_set['results'],
|
||||
'finished' => &$finished,
|
||||
'message' => &$task_message,
|
||||
);
|
||||
call_user_func_array($callback, array_merge($args, array(&$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'] = array();
|
||||
}
|
||||
}
|
||||
|
||||
// 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'];
|
||||
$total = $old_set['total'];
|
||||
$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;
|
||||
$percentage = _batch_api_percentage($total, $current);
|
||||
$elapsed = isset($current_set['elapsed']) ? $current_set['elapsed'] : 0;
|
||||
$values = array(
|
||||
'@remaining' => $remaining,
|
||||
'@total' => $total,
|
||||
'@current' => floor($current),
|
||||
'@percentage' => $percentage,
|
||||
'@elapsed' => \Drupal::service('date.formatter')->formatInterval($elapsed / 1000),
|
||||
// If possible, estimate remaining processing time.
|
||||
'@estimate' => ($current > 0) ? \Drupal::service('date.formatter')->formatInterval(($elapsed * ($total - $current) / $current) / 1000) : '-',
|
||||
);
|
||||
$message = strtr($progress_message, $values);
|
||||
if (!empty($task_message)) {
|
||||
$label = $task_message;
|
||||
}
|
||||
|
||||
return array($percentage, $message, $label);
|
||||
}
|
||||
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 $total
|
||||
* The total number of operations.
|
||||
* @param $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
|
||||
* 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 if a subsequent set was found in the batch.
|
||||
*/
|
||||
function _batch_next_set() {
|
||||
$batch = &batch_get();
|
||||
if (isset($batch['sets'][$batch['current_set'] + 1])) {
|
||||
$batch['current_set']++;
|
||||
$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, array(&$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();
|
||||
|
||||
// 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();
|
||||
call_user_func_array($batch_set['finished'], array($batch_set['success'], $batch_set['results'], $operations, \Drupal::service('date.formatter')->formatInterval($batch_set['elapsed'] / 1000)));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// 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.
|
||||
if (isset($_SESSION)) {
|
||||
unset($_SESSION['batches'][$batch['id']]);
|
||||
if (empty($_SESSION['batches'])) {
|
||||
unset($_SESSION['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 (!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['batch_form_state'] = $_batch['form_state'];
|
||||
}
|
||||
$callback = $_batch['redirect_callback'];
|
||||
/** @var \Drupal\Core\Url $source_url */
|
||||
$source_url = $_batch['source_url'];
|
||||
if (is_callable($callback)) {
|
||||
$callback($_batch['source_url'], array('query' => array('op' => 'finish', 'id' => $_batch['id'])));
|
||||
}
|
||||
elseif ($callback === NULL) {
|
||||
// Default to RedirectResponse objects when nothing specified.
|
||||
$url = $source_url
|
||||
->setAbsolute()
|
||||
->setOption('query', ['op' => 'finish', 'id' => $_batch['id']]);
|
||||
return new RedirectResponse($url->toString());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Shutdown function: Stores the current batch data for the next request.
|
||||
*
|
||||
* @see _batch_page()
|
||||
* @see drupal_register_shutdown_function()
|
||||
*/
|
||||
function _batch_shutdown() {
|
||||
if ($batch = batch_get()) {
|
||||
// Update safe strings.
|
||||
// @todo Ensure we are not storing an excessively large string list in:
|
||||
// https://www.drupal.org/node/2295823
|
||||
$batch['safe_strings'] = SafeMarkup::getAll();
|
||||
\Drupal::service('batch.storage')->update($batch);
|
||||
}
|
||||
}
|
1132
core/includes/bootstrap.inc
Normal file
1132
core/includes/bootstrap.inc
Normal file
File diff suppressed because it is too large
Load diff
1493
core/includes/common.inc
Normal file
1493
core/includes/common.inc
Normal file
File diff suppressed because it is too large
Load diff
760
core/includes/database.inc
Normal file
760
core/includes/database.inc
Normal file
|
@ -0,0 +1,760 @@
|
|||
<?php
|
||||
|
||||
use Drupal\Core\Database\Database;
|
||||
use Drupal\Core\Database\Query\Condition;
|
||||
use Drupal\Core\Site\Settings;
|
||||
|
||||
/**
|
||||
* @file
|
||||
* Core systems for the database layer.
|
||||
*
|
||||
* Classes required for basic functioning of the database system should be
|
||||
* placed in this file. All utility functions should also be placed in this
|
||||
* file only, as they cannot auto-load the way classes can.
|
||||
*/
|
||||
|
||||
/**
|
||||
* @addtogroup database
|
||||
* @{
|
||||
*/
|
||||
|
||||
/**
|
||||
* Executes an arbitrary query string against the active database.
|
||||
*
|
||||
* Use this function for SELECT queries if it is just a simple query string.
|
||||
* If the caller or other modules need to change the query, use db_select()
|
||||
* instead.
|
||||
*
|
||||
* Do not use this function for INSERT, UPDATE, or DELETE queries. Those should
|
||||
* be handled via db_insert(), db_update() and db_delete() respectively.
|
||||
*
|
||||
* @param $query
|
||||
* The prepared statement query to run. Although it will accept both named and
|
||||
* unnamed placeholders, named placeholders are strongly preferred as they are
|
||||
* more self-documenting. If the argument corresponding to a placeholder is
|
||||
* an array of values to be expanded, e.g. for an IN query, the placeholder
|
||||
* should be named with a trailing bracket like :example[]
|
||||
* @param $args
|
||||
* An array of values to substitute into the query. If the query uses named
|
||||
* placeholders, this is an associative array in any order. If the query uses
|
||||
* unnamed placeholders (?), this is an indexed array and the order must match
|
||||
* the order of placeholders in the query string.
|
||||
* @param $options
|
||||
* An array of options to control how the query operates.
|
||||
*
|
||||
* @return \Drupal\Core\Database\StatementInterface
|
||||
* A prepared statement object, already executed.
|
||||
*
|
||||
* @see \Drupal\Core\Database\Connection::defaultOptions()
|
||||
*/
|
||||
function db_query($query, array $args = array(), array $options = array()) {
|
||||
if (empty($options['target'])) {
|
||||
$options['target'] = 'default';
|
||||
}
|
||||
|
||||
return Database::getConnection($options['target'])->query($query, $args, $options);
|
||||
}
|
||||
|
||||
/**
|
||||
* Executes a query against the active database, restricted to a range.
|
||||
*
|
||||
* @param $query
|
||||
* The prepared statement query to run. Although it will accept both named and
|
||||
* unnamed placeholders, named placeholders are strongly preferred as they are
|
||||
* more self-documenting.
|
||||
* @param $from
|
||||
* The first record from the result set to return.
|
||||
* @param $count
|
||||
* The number of records to return from the result set.
|
||||
* @param $args
|
||||
* An array of values to substitute into the query. If the query uses named
|
||||
* placeholders, this is an associative array in any order. If the query uses
|
||||
* unnamed placeholders (?), this is an indexed array and the order must match
|
||||
* the order of placeholders in the query string.
|
||||
* @param $options
|
||||
* An array of options to control how the query operates.
|
||||
*
|
||||
* @return \Drupal\Core\Database\StatementInterface
|
||||
* A prepared statement object, already executed.
|
||||
*
|
||||
* @see \Drupal\Core\Database\Connection::defaultOptions()
|
||||
*/
|
||||
function db_query_range($query, $from, $count, array $args = array(), array $options = array()) {
|
||||
if (empty($options['target'])) {
|
||||
$options['target'] = 'default';
|
||||
}
|
||||
|
||||
return Database::getConnection($options['target'])->queryRange($query, $from, $count, $args, $options);
|
||||
}
|
||||
|
||||
/**
|
||||
* Executes a SELECT query string and saves the result set to a temporary table.
|
||||
*
|
||||
* The execution of the query string happens against the active database.
|
||||
*
|
||||
* @param $query
|
||||
* The prepared SELECT statement query to run. Although it will accept both
|
||||
* named and unnamed placeholders, named placeholders are strongly preferred
|
||||
* as they are more self-documenting.
|
||||
* @param $args
|
||||
* An array of values to substitute into the query. If the query uses named
|
||||
* placeholders, this is an associative array in any order. If the query uses
|
||||
* unnamed placeholders (?), this is an indexed array and the order must match
|
||||
* the order of placeholders in the query string.
|
||||
* @param $options
|
||||
* An array of options to control how the query operates.
|
||||
*
|
||||
* @return
|
||||
* The name of the temporary table.
|
||||
*
|
||||
* @see \Drupal\Core\Database\Connection::defaultOptions()
|
||||
*/
|
||||
function db_query_temporary($query, array $args = array(), array $options = array()) {
|
||||
if (empty($options['target'])) {
|
||||
$options['target'] = 'default';
|
||||
}
|
||||
|
||||
return Database::getConnection($options['target'])->queryTemporary($query, $args, $options);
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns a new InsertQuery object for the active database.
|
||||
*
|
||||
* @param $table
|
||||
* The table into which to insert.
|
||||
* @param $options
|
||||
* An array of options to control how the query operates.
|
||||
*
|
||||
* @return \Drupal\Core\Database\Query\Insert
|
||||
* A new Insert object for this connection.
|
||||
*/
|
||||
function db_insert($table, array $options = array()) {
|
||||
if (empty($options['target']) || $options['target'] == 'replica') {
|
||||
$options['target'] = 'default';
|
||||
}
|
||||
return Database::getConnection($options['target'])->insert($table, $options);
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns a new MergeQuery object for the active database.
|
||||
*
|
||||
* @param $table
|
||||
* The table into which to merge.
|
||||
* @param $options
|
||||
* An array of options to control how the query operates.
|
||||
*
|
||||
* @return \Drupal\Core\Database\Query\Merge
|
||||
* A new Merge object for this connection.
|
||||
*/
|
||||
function db_merge($table, array $options = array()) {
|
||||
if (empty($options['target']) || $options['target'] == 'replica') {
|
||||
$options['target'] = 'default';
|
||||
}
|
||||
return Database::getConnection($options['target'])->merge($table, $options);
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns a new UpdateQuery object for the active database.
|
||||
*
|
||||
* @param $table
|
||||
* The table to update.
|
||||
* @param $options
|
||||
* An array of options to control how the query operates.
|
||||
*
|
||||
* @return \Drupal\Core\Database\Query\Update
|
||||
* A new Update object for this connection.
|
||||
*/
|
||||
function db_update($table, array $options = array()) {
|
||||
if (empty($options['target']) || $options['target'] == 'replica') {
|
||||
$options['target'] = 'default';
|
||||
}
|
||||
return Database::getConnection($options['target'])->update($table, $options);
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns a new DeleteQuery object for the active database.
|
||||
*
|
||||
* @param $table
|
||||
* The table from which to delete.
|
||||
* @param $options
|
||||
* An array of options to control how the query operates.
|
||||
*
|
||||
* @return \Drupal\Core\Database\Query\Delete
|
||||
* A new Delete object for this connection.
|
||||
*/
|
||||
function db_delete($table, array $options = array()) {
|
||||
if (empty($options['target']) || $options['target'] == 'replica') {
|
||||
$options['target'] = 'default';
|
||||
}
|
||||
return Database::getConnection($options['target'])->delete($table, $options);
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns a new TruncateQuery object for the active database.
|
||||
*
|
||||
* @param $table
|
||||
* The table from which to delete.
|
||||
* @param $options
|
||||
* An array of options to control how the query operates.
|
||||
*
|
||||
* @return \Drupal\Core\Database\Query\Truncate
|
||||
* A new Truncate object for this connection.
|
||||
*/
|
||||
function db_truncate($table, array $options = array()) {
|
||||
if (empty($options['target']) || $options['target'] == 'replica') {
|
||||
$options['target'] = 'default';
|
||||
}
|
||||
return Database::getConnection($options['target'])->truncate($table, $options);
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns a new SelectQuery object for the active database.
|
||||
*
|
||||
* @param $table
|
||||
* The base table for this query. May be a string or another SelectQuery
|
||||
* object. If a query object is passed, it will be used as a subselect.
|
||||
* @param $alias
|
||||
* (optional) The alias for the base table of this query.
|
||||
* @param $options
|
||||
* (optional) An array of options to control how the query operates.
|
||||
*
|
||||
* @return \Drupal\Core\Database\Query\Select
|
||||
* A new Select object for this connection.
|
||||
*/
|
||||
function db_select($table, $alias = NULL, array $options = array()) {
|
||||
if (empty($options['target'])) {
|
||||
$options['target'] = 'default';
|
||||
}
|
||||
return Database::getConnection($options['target'])->select($table, $alias, $options);
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns a new transaction object for the active database.
|
||||
*
|
||||
* @param string $name
|
||||
* Optional name of the transaction.
|
||||
* @param array $options
|
||||
* An array of options to control how the transaction operates:
|
||||
* - target: The database target name.
|
||||
*
|
||||
* @return \Drupal\Core\Database\Transaction
|
||||
* A new Transaction object for this connection.
|
||||
*/
|
||||
function db_transaction($name = NULL, array $options = array()) {
|
||||
if (empty($options['target'])) {
|
||||
$options['target'] = 'default';
|
||||
}
|
||||
return Database::getConnection($options['target'])->startTransaction($name);
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets a new active database.
|
||||
*
|
||||
* @param $key
|
||||
* The key in the $databases array to set as the default database.
|
||||
*
|
||||
* @return
|
||||
* The key of the formerly active database.
|
||||
*/
|
||||
function db_set_active($key = 'default') {
|
||||
return Database::setActiveConnection($key);
|
||||
}
|
||||
|
||||
/**
|
||||
* Restricts a dynamic table name to safe characters.
|
||||
*
|
||||
* Only keeps alphanumeric and underscores.
|
||||
*
|
||||
* @param $table
|
||||
* The table name to escape.
|
||||
*
|
||||
* @return
|
||||
* The escaped table name as a string.
|
||||
*/
|
||||
function db_escape_table($table) {
|
||||
return Database::getConnection()->escapeTable($table);
|
||||
}
|
||||
|
||||
/**
|
||||
* Restricts a dynamic column or constraint name to safe characters.
|
||||
*
|
||||
* Only keeps alphanumeric and underscores.
|
||||
*
|
||||
* @param $field
|
||||
* The field name to escape.
|
||||
*
|
||||
* @return
|
||||
* The escaped field name as a string.
|
||||
*/
|
||||
function db_escape_field($field) {
|
||||
return Database::getConnection()->escapeField($field);
|
||||
}
|
||||
|
||||
/**
|
||||
* Escapes characters that work as wildcard characters in a LIKE pattern.
|
||||
*
|
||||
* The wildcard characters "%" and "_" as well as backslash are prefixed with
|
||||
* a backslash. Use this to do a search for a verbatim string without any
|
||||
* wildcard behavior.
|
||||
*
|
||||
* You must use a query builder like db_select() in order to use db_like() on
|
||||
* all supported database systems. Using db_like() with db_query() or
|
||||
* db_query_range() is not supported.
|
||||
*
|
||||
* For example, the following does a case-insensitive query for all rows whose
|
||||
* name starts with $prefix:
|
||||
* @code
|
||||
* $result = db_select('person', 'p')
|
||||
* ->fields('p')
|
||||
* ->condition('name', db_like($prefix) . '%', 'LIKE')
|
||||
* ->execute()
|
||||
* ->fetchAll();
|
||||
* @endcode
|
||||
*
|
||||
* Backslash is defined as escape character for LIKE patterns in
|
||||
* DatabaseCondition::mapConditionOperator().
|
||||
*
|
||||
* @param $string
|
||||
* The string to escape.
|
||||
*
|
||||
* @return
|
||||
* The escaped string.
|
||||
*/
|
||||
function db_like($string) {
|
||||
return Database::getConnection()->escapeLike($string);
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieves the name of the currently active database driver.
|
||||
*
|
||||
* @return
|
||||
* The name of the currently active database driver.
|
||||
*/
|
||||
function db_driver() {
|
||||
return Database::getConnection()->driver();
|
||||
}
|
||||
|
||||
/**
|
||||
* Closes the active database connection.
|
||||
*
|
||||
* @param $options
|
||||
* An array of options to control which connection is closed. Only the target
|
||||
* key has any meaning in this case.
|
||||
*/
|
||||
function db_close(array $options = array()) {
|
||||
if (empty($options['target'])) {
|
||||
$options['target'] = NULL;
|
||||
}
|
||||
Database::closeConnection($options['target']);
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieves a unique id.
|
||||
*
|
||||
* Use this function if for some reason you can't use a serial field. Using a
|
||||
* serial field is preferred, and InsertQuery::execute() returns the value of
|
||||
* the last ID inserted.
|
||||
*
|
||||
* @param $existing_id
|
||||
* After a database import, it might be that the sequences table is behind, so
|
||||
* by passing in a minimum ID, it can be assured that we never issue the same
|
||||
* ID.
|
||||
*
|
||||
* @return
|
||||
* An integer number larger than any number returned before for this sequence.
|
||||
*/
|
||||
function db_next_id($existing_id = 0) {
|
||||
return Database::getConnection()->nextId($existing_id);
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns a new DatabaseCondition, set to "OR" all conditions together.
|
||||
*
|
||||
* @return \Drupal\Core\Database\Query\Condition
|
||||
* A new Condition object, set to "OR" all conditions together.
|
||||
*/
|
||||
function db_or() {
|
||||
return new Condition('OR');
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns a new DatabaseCondition, set to "AND" all conditions together.
|
||||
*
|
||||
* @return \Drupal\Core\Database\Query\Condition
|
||||
* A new Condition object, set to "AND" all conditions together.
|
||||
*/
|
||||
function db_and() {
|
||||
return new Condition('AND');
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns a new DatabaseCondition, set to "XOR" all conditions together.
|
||||
*
|
||||
* @return \Drupal\Core\Database\Query\Condition
|
||||
* A new Condition object, set to "XOR" all conditions together.
|
||||
*/
|
||||
function db_xor() {
|
||||
return new Condition('XOR');
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns a new DatabaseCondition, set to the specified conjunction.
|
||||
*
|
||||
* Internal API function call. The db_and(), db_or(), and db_xor()
|
||||
* functions are preferred.
|
||||
*
|
||||
* @param $conjunction
|
||||
* The conjunction to use for query conditions (AND, OR or XOR).
|
||||
*
|
||||
* @return \Drupal\Core\Database\Query\Condition
|
||||
* A new Condition object, set to the specified conjunction.
|
||||
*/
|
||||
function db_condition($conjunction) {
|
||||
return new Condition($conjunction);
|
||||
}
|
||||
|
||||
/**
|
||||
* @} End of "addtogroup database".
|
||||
*/
|
||||
|
||||
|
||||
/**
|
||||
* @addtogroup schemaapi
|
||||
* @{
|
||||
*/
|
||||
|
||||
/**
|
||||
* Creates a new table from a Drupal table definition.
|
||||
*
|
||||
* @param $name
|
||||
* The name of the table to create.
|
||||
* @param $table
|
||||
* A Schema API table definition array.
|
||||
*/
|
||||
function db_create_table($name, $table) {
|
||||
return Database::getConnection()->schema()->createTable($name, $table);
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns an array of field names from an array of key/index column specifiers.
|
||||
*
|
||||
* This is usually an identity function but if a key/index uses a column prefix
|
||||
* specification, this function extracts just the name.
|
||||
*
|
||||
* @param $fields
|
||||
* An array of key/index column specifiers.
|
||||
*
|
||||
* @return
|
||||
* An array of field names.
|
||||
*/
|
||||
function db_field_names($fields) {
|
||||
return Database::getConnection()->schema()->fieldNames($fields);
|
||||
}
|
||||
|
||||
/**
|
||||
* Checks if an index exists in the given table.
|
||||
*
|
||||
* @param $table
|
||||
* The name of the table in drupal (no prefixing).
|
||||
* @param $name
|
||||
* The name of the index in drupal (no prefixing).
|
||||
*
|
||||
* @return
|
||||
* TRUE if the given index exists, otherwise FALSE.
|
||||
*/
|
||||
function db_index_exists($table, $name) {
|
||||
return Database::getConnection()->schema()->indexExists($table, $name);
|
||||
}
|
||||
|
||||
/**
|
||||
* Checks if a table exists.
|
||||
*
|
||||
* @param $table
|
||||
* The name of the table in drupal (no prefixing).
|
||||
*
|
||||
* @return
|
||||
* TRUE if the given table exists, otherwise FALSE.
|
||||
*/
|
||||
function db_table_exists($table) {
|
||||
return Database::getConnection()->schema()->tableExists($table);
|
||||
}
|
||||
|
||||
/**
|
||||
* Checks if a column exists in the given table.
|
||||
*
|
||||
* @param $table
|
||||
* The name of the table in drupal (no prefixing).
|
||||
* @param $field
|
||||
* The name of the field.
|
||||
*
|
||||
* @return
|
||||
* TRUE if the given column exists, otherwise FALSE.
|
||||
*/
|
||||
function db_field_exists($table, $field) {
|
||||
return Database::getConnection()->schema()->fieldExists($table, $field);
|
||||
}
|
||||
|
||||
/**
|
||||
* Finds all tables that are like the specified base table name.
|
||||
*
|
||||
* @param $table_expression
|
||||
* An SQL expression, for example "simpletest%" (without the quotes).
|
||||
* BEWARE: this is not prefixed, the caller should take care of that.
|
||||
*
|
||||
* @return
|
||||
* Array, both the keys and the values are the matching tables.
|
||||
*/
|
||||
function db_find_tables($table_expression) {
|
||||
return Database::getConnection()->schema()->findTables($table_expression);
|
||||
}
|
||||
|
||||
function _db_create_keys_sql($spec) {
|
||||
return Database::getConnection()->schema()->createKeysSql($spec);
|
||||
}
|
||||
|
||||
/**
|
||||
* Renames a table.
|
||||
*
|
||||
* @param $table
|
||||
* The current name of the table to be renamed.
|
||||
* @param $new_name
|
||||
* The new name for the table.
|
||||
*/
|
||||
function db_rename_table($table, $new_name) {
|
||||
return Database::getConnection()->schema()->renameTable($table, $new_name);
|
||||
}
|
||||
|
||||
/**
|
||||
* Drops a table.
|
||||
*
|
||||
* @param $table
|
||||
* The table to be dropped.
|
||||
*/
|
||||
function db_drop_table($table) {
|
||||
return Database::getConnection()->schema()->dropTable($table);
|
||||
}
|
||||
|
||||
/**
|
||||
* Adds a new field to a table.
|
||||
*
|
||||
* @param $table
|
||||
* Name of the table to be altered.
|
||||
* @param $field
|
||||
* Name of the field to be added.
|
||||
* @param $spec
|
||||
* The field specification array, as taken from a schema definition. The
|
||||
* specification may also contain the key 'initial'; the newly-created field
|
||||
* will be set to the value of the key in all rows. This is most useful for
|
||||
* creating NOT NULL columns with no default value in existing tables.
|
||||
* @param $keys_new
|
||||
* (optional) Keys and indexes specification to be created on the table along
|
||||
* with adding the field. The format is the same as a table specification, but
|
||||
* without the 'fields' element. If you are adding a type 'serial' field, you
|
||||
* MUST specify at least one key or index including it in this array. See
|
||||
* db_change_field() for more explanation why.
|
||||
*
|
||||
* @see db_change_field()
|
||||
*/
|
||||
function db_add_field($table, $field, $spec, $keys_new = array()) {
|
||||
return Database::getConnection()->schema()->addField($table, $field, $spec, $keys_new);
|
||||
}
|
||||
|
||||
/**
|
||||
* Drops a field.
|
||||
*
|
||||
* @param $table
|
||||
* The table to be altered.
|
||||
* @param $field
|
||||
* The field to be dropped.
|
||||
*/
|
||||
function db_drop_field($table, $field) {
|
||||
return Database::getConnection()->schema()->dropField($table, $field);
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the default value for a field.
|
||||
*
|
||||
* @param $table
|
||||
* The table to be altered.
|
||||
* @param $field
|
||||
* The field to be altered.
|
||||
* @param $default
|
||||
* Default value to be set. NULL for 'default NULL'.
|
||||
*/
|
||||
function db_field_set_default($table, $field, $default) {
|
||||
return Database::getConnection()->schema()->fieldSetDefault($table, $field, $default);
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets a field to have no default value.
|
||||
*
|
||||
* @param $table
|
||||
* The table to be altered.
|
||||
* @param $field
|
||||
* The field to be altered.
|
||||
*/
|
||||
function db_field_set_no_default($table, $field) {
|
||||
return Database::getConnection()->schema()->fieldSetNoDefault($table, $field);
|
||||
}
|
||||
|
||||
/**
|
||||
* Adds a primary key to a database table.
|
||||
*
|
||||
* @param $table
|
||||
* Name of the table to be altered.
|
||||
* @param $fields
|
||||
* Array of fields for the primary key.
|
||||
*/
|
||||
function db_add_primary_key($table, $fields) {
|
||||
return Database::getConnection()->schema()->addPrimaryKey($table, $fields);
|
||||
}
|
||||
|
||||
/**
|
||||
* Drops the primary key of a database table.
|
||||
*
|
||||
* @param $table
|
||||
* Name of the table to be altered.
|
||||
*/
|
||||
function db_drop_primary_key($table) {
|
||||
return Database::getConnection()->schema()->dropPrimaryKey($table);
|
||||
}
|
||||
|
||||
/**
|
||||
* Adds a unique key.
|
||||
*
|
||||
* @param $table
|
||||
* The table to be altered.
|
||||
* @param $name
|
||||
* The name of the key.
|
||||
* @param $fields
|
||||
* An array of field names.
|
||||
*/
|
||||
function db_add_unique_key($table, $name, $fields) {
|
||||
return Database::getConnection()->schema()->addUniqueKey($table, $name, $fields);
|
||||
}
|
||||
|
||||
/**
|
||||
* Drops a unique key.
|
||||
*
|
||||
* @param $table
|
||||
* The table to be altered.
|
||||
* @param $name
|
||||
* The name of the key.
|
||||
*/
|
||||
function db_drop_unique_key($table, $name) {
|
||||
return Database::getConnection()->schema()->dropUniqueKey($table, $name);
|
||||
}
|
||||
|
||||
/**
|
||||
* Adds an index.
|
||||
*
|
||||
* @param $table
|
||||
* The table to be altered.
|
||||
* @param $name
|
||||
* The name of the index.
|
||||
* @param $fields
|
||||
* An array of field names.
|
||||
*/
|
||||
function db_add_index($table, $name, $fields) {
|
||||
return Database::getConnection()->schema()->addIndex($table, $name, $fields);
|
||||
}
|
||||
|
||||
/**
|
||||
* Drops an index.
|
||||
*
|
||||
* @param $table
|
||||
* The table to be altered.
|
||||
* @param $name
|
||||
* The name of the index.
|
||||
*/
|
||||
function db_drop_index($table, $name) {
|
||||
return Database::getConnection()->schema()->dropIndex($table, $name);
|
||||
}
|
||||
|
||||
/**
|
||||
* Changes a field definition.
|
||||
*
|
||||
* IMPORTANT NOTE: To maintain database portability, you have to explicitly
|
||||
* recreate all indices and primary keys that are using the changed field.
|
||||
*
|
||||
* That means that you have to drop all affected keys and indexes with
|
||||
* db_drop_{primary_key,unique_key,index}() before calling db_change_field().
|
||||
* To recreate the keys and indices, pass the key definitions as the optional
|
||||
* $keys_new argument directly to db_change_field().
|
||||
*
|
||||
* For example, suppose you have:
|
||||
* @code
|
||||
* $schema['foo'] = array(
|
||||
* 'fields' => array(
|
||||
* 'bar' => array('type' => 'int', 'not null' => TRUE)
|
||||
* ),
|
||||
* 'primary key' => array('bar')
|
||||
* );
|
||||
* @endcode
|
||||
* and you want to change foo.bar to be type serial, leaving it as the primary
|
||||
* key. The correct sequence is:
|
||||
* @code
|
||||
* db_drop_primary_key('foo');
|
||||
* db_change_field('foo', 'bar', 'bar',
|
||||
* array('type' => 'serial', 'not null' => TRUE),
|
||||
* array('primary key' => array('bar')));
|
||||
* @endcode
|
||||
*
|
||||
* The reasons for this are due to the different database engines:
|
||||
*
|
||||
* On PostgreSQL, changing a field definition involves adding a new field and
|
||||
* dropping an old one which causes any indices, primary keys and sequences
|
||||
* (from serial-type fields) that use the changed field to be dropped.
|
||||
*
|
||||
* On MySQL, all type 'serial' fields must be part of at least one key or index
|
||||
* as soon as they are created. You cannot use
|
||||
* db_add_{primary_key,unique_key,index}() for this purpose because the ALTER
|
||||
* TABLE command will fail to add the column without a key or index
|
||||
* specification. The solution is to use the optional $keys_new argument to
|
||||
* create the key or index at the same time as field.
|
||||
*
|
||||
* You could use db_add_{primary_key,unique_key,index}() in all cases unless you
|
||||
* are converting a field to be type serial. You can use the $keys_new argument
|
||||
* in all cases.
|
||||
*
|
||||
* @param $table
|
||||
* Name of the table.
|
||||
* @param $field
|
||||
* Name of the field to change.
|
||||
* @param $field_new
|
||||
* New name for the field (set to the same as $field if you don't want to
|
||||
* change the name).
|
||||
* @param $spec
|
||||
* The field specification for the new field.
|
||||
* @param $keys_new
|
||||
* (optional) Keys and indexes specification to be created on the table along
|
||||
* with changing the field. The format is the same as a table specification
|
||||
* but without the 'fields' element.
|
||||
*/
|
||||
function db_change_field($table, $field, $field_new, $spec, $keys_new = array()) {
|
||||
return Database::getConnection()->schema()->changeField($table, $field, $field_new, $spec, $keys_new);
|
||||
}
|
||||
|
||||
/**
|
||||
* @} End of "addtogroup schemaapi".
|
||||
*/
|
||||
|
||||
/**
|
||||
* Sets a session variable specifying the lag time for ignoring a replica
|
||||
* server (A replica server is traditionally referred to as
|
||||
* a "slave" in database server documentation).
|
||||
* @see https://www.drupal.org/node/2275877
|
||||
*/
|
||||
function db_ignore_replica() {
|
||||
$connection_info = Database::getConnectionInfo();
|
||||
// Only set ignore_replica_server if there are replica servers being used,
|
||||
// which is assumed if there are more than one.
|
||||
if (count($connection_info) > 1) {
|
||||
// Five minutes is long enough to allow the replica to break and resume
|
||||
// interrupted replication without causing problems on the Drupal site from
|
||||
// the old data.
|
||||
$duration = Settings::get('maximum_replication_lag', 300);
|
||||
// Set session variable with amount of time to delay before using replica.
|
||||
$_SESSION['ignore_replica_server'] = REQUEST_TIME + $duration;
|
||||
}
|
||||
}
|
415
core/includes/entity.inc
Normal file
415
core/includes/entity.inc
Normal file
|
@ -0,0 +1,415 @@
|
|||
<?php
|
||||
|
||||
/**
|
||||
* @file
|
||||
* Entity API for handling entities like nodes or users.
|
||||
*/
|
||||
|
||||
use Drupal\Core\Cache\Cache;
|
||||
use Drupal\Core\Entity\EntityStorageException;
|
||||
use Drupal\Core\Entity\EntityInterface;
|
||||
|
||||
/**
|
||||
* Clears the entity render cache for all entity types.
|
||||
*/
|
||||
function entity_render_cache_clear() {
|
||||
$entity_manager = Drupal::entityManager();
|
||||
foreach ($entity_manager->getDefinitions() as $entity_type => $info) {
|
||||
if ($entity_manager->hasHandler($entity_type, 'view_builder')) {
|
||||
$entity_manager->getViewBuilder($entity_type)->resetCache();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the entity bundle info.
|
||||
*
|
||||
* @param string|null $entity_type
|
||||
* The entity type whose bundle info should be returned, or NULL for all
|
||||
* bundles info. Defaults to NULL.
|
||||
*
|
||||
* @return array
|
||||
* The bundle info for a specific entity type, or all entity types.
|
||||
*
|
||||
* @see \Drupal\Core\Entity\EntityManagerInterface::getBundleInfo()
|
||||
* @see \Drupal\Core\Entity\EntityManagerInterface::getAllBundleInfo()
|
||||
*/
|
||||
function entity_get_bundles($entity_type = NULL) {
|
||||
if (isset($entity_type)) {
|
||||
return \Drupal::entityManager()->getBundleInfo($entity_type);
|
||||
}
|
||||
else {
|
||||
return \Drupal::entityManager()->getAllBundleInfo();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Loads an entity from the database.
|
||||
*
|
||||
* @param string $entity_type
|
||||
* The entity type to load, e.g. node or user.
|
||||
* @param mixed $id
|
||||
* The id of the entity to load.
|
||||
* @param bool $reset
|
||||
* Whether to reset the internal cache for the requested entity type.
|
||||
*
|
||||
* @return \Drupal\Core\Entity\EntityInterface|null
|
||||
* The entity object, or NULL if there is no entity with the given ID.
|
||||
*
|
||||
* @see \Drupal\Core\Entity\EntityManagerInterface
|
||||
* @see \Drupal\Core\Entity\EntityStorageInterface
|
||||
* @see \Drupal\Core\Entity\Sql\SqlContentEntityStorage
|
||||
* @see \Drupal\Core\Entity\Query\QueryInterface
|
||||
*/
|
||||
function entity_load($entity_type, $id, $reset = FALSE) {
|
||||
$controller = \Drupal::entityManager()->getStorage($entity_type);
|
||||
if ($reset) {
|
||||
$controller->resetCache(array($id));
|
||||
}
|
||||
return $controller->load($id);
|
||||
}
|
||||
|
||||
/**
|
||||
* Loads an entity from the database.
|
||||
*
|
||||
* @param string $entity_type
|
||||
* The entity type to load, e.g. node or user.
|
||||
* @param int $revision_id
|
||||
* The id of the entity to load.
|
||||
*
|
||||
* @return \Drupal\Core\Entity\EntityInterface|null
|
||||
* The entity object, or NULL if there is no entity with the given revision
|
||||
* id.
|
||||
*
|
||||
* @see \Drupal\Core\Entity\EntityManagerInterface
|
||||
* @see \Drupal\Core\Entity\EntityStorageInterface
|
||||
* @see \Drupal\Core\Entity\Sql\SqlContentEntityStorage
|
||||
*/
|
||||
function entity_revision_load($entity_type, $revision_id) {
|
||||
return \Drupal::entityManager()
|
||||
->getStorage($entity_type)
|
||||
->loadRevision($revision_id);
|
||||
}
|
||||
|
||||
/**
|
||||
* Deletes an entity revision.
|
||||
*
|
||||
* @param string $entity_type
|
||||
* The entity type to load, e.g. node or user.
|
||||
* @param $revision_id
|
||||
* The revision ID to delete.
|
||||
*/
|
||||
function entity_revision_delete($entity_type, $revision_id) {
|
||||
\Drupal::entityManager()
|
||||
->getStorage($entity_type)
|
||||
->deleteRevision($revision_id);
|
||||
}
|
||||
|
||||
/**
|
||||
* Loads multiple entities from the database.
|
||||
*
|
||||
* This function should be used whenever you need to load more than one entity
|
||||
* from the database. The entities are loaded into memory and will not require
|
||||
* database access if loaded again during the same page request.
|
||||
*
|
||||
* The actual loading is done through a class that has to implement the
|
||||
* Drupal\Core\Entity\EntityStorageInterface interface. By default,
|
||||
* Drupal\Core\Entity\Sql\SqlContentEntityStorage is used for content entities
|
||||
* and Drupal\Core\Config\Entity\ConfigEntityStorage for config entities. Entity
|
||||
* types can specify that a different class should be used by setting the
|
||||
* "controllers['storage']" key in the entity plugin annotation. These classes
|
||||
* can either implement the Drupal\Core\Entity\EntityStorageInterface
|
||||
* interface, or, most commonly, extend the
|
||||
* Drupal\Core\Entity\Sql\SqlContentEntityStorage class.
|
||||
* See Drupal\node\Entity\Node and Drupal\node\NodeStorage
|
||||
* for an example.
|
||||
*
|
||||
* @param string $entity_type
|
||||
* The entity type to load, e.g. node or user.
|
||||
* @param array $ids
|
||||
* (optional) An array of entity IDs. If omitted, all entities are loaded.
|
||||
* @param bool $reset
|
||||
* Whether to reset the internal cache for the requested entity type.
|
||||
*
|
||||
* @return array
|
||||
* An array of entity objects indexed by their IDs.
|
||||
*
|
||||
* @see \Drupal\Core\Entity\EntityManagerInterface
|
||||
* @see \Drupal\Core\Entity\EntityStorageInterface
|
||||
* @see \Drupal\Core\Entity\Sql\SqlContentEntityStorage
|
||||
* @see \Drupal\Core\Entity\Query\QueryInterface
|
||||
*/
|
||||
function entity_load_multiple($entity_type, array $ids = NULL, $reset = FALSE) {
|
||||
$controller = \Drupal::entityManager()->getStorage($entity_type);
|
||||
if ($reset) {
|
||||
$controller->resetCache($ids);
|
||||
}
|
||||
return $controller->loadMultiple($ids);
|
||||
}
|
||||
|
||||
/**
|
||||
* Load entities by their property values.
|
||||
*
|
||||
* @param string $entity_type
|
||||
* The entity type to load, e.g. node or user.
|
||||
* @param array $values
|
||||
* An associative array where the keys are the property names and the
|
||||
* values are the values those properties must have.
|
||||
*
|
||||
* @return array
|
||||
* An array of entity objects indexed by their IDs. Returns an empty array if
|
||||
* no matching entities are found.
|
||||
*/
|
||||
function entity_load_multiple_by_properties($entity_type, array $values) {
|
||||
return \Drupal::entityManager()
|
||||
->getStorage($entity_type)
|
||||
->loadByProperties($values);
|
||||
}
|
||||
|
||||
/**
|
||||
* Loads the unchanged, i.e. not modified, entity from the database.
|
||||
*
|
||||
* Unlike entity_load() this function ensures the entity is directly loaded from
|
||||
* the database, thus bypassing any static cache. In particular, this function
|
||||
* is useful to determine changes by comparing the entity being saved to the
|
||||
* stored entity.
|
||||
*
|
||||
* @param $entity_type
|
||||
* The entity type to load, e.g. node or user.
|
||||
* @param $id
|
||||
* The ID of the entity to load.
|
||||
*
|
||||
* @return
|
||||
* The unchanged entity, or FALSE if the entity cannot be loaded.
|
||||
*/
|
||||
function entity_load_unchanged($entity_type, $id) {
|
||||
return \Drupal::entityManager()
|
||||
->getStorage($entity_type)
|
||||
->loadUnchanged($id);
|
||||
}
|
||||
|
||||
/**
|
||||
* Deletes multiple entities permanently.
|
||||
*
|
||||
* @param string $entity_type
|
||||
* The type of the entity.
|
||||
* @param array $ids
|
||||
* An array of entity IDs of the entities to delete.
|
||||
*/
|
||||
function entity_delete_multiple($entity_type, array $ids) {
|
||||
$controller = \Drupal::entityManager()->getStorage($entity_type);
|
||||
$entities = $controller->loadMultiple($ids);
|
||||
$controller->delete($entities);
|
||||
}
|
||||
|
||||
/**
|
||||
* Constructs a new entity object, without permanently saving it.
|
||||
*
|
||||
* @param string $entity_type
|
||||
* The type of the entity.
|
||||
* @param array $values
|
||||
* (optional) An array of values to set, keyed by property name. If the
|
||||
* entity type has bundles, the bundle key has to be specified.
|
||||
*
|
||||
* @return \Drupal\Core\Entity\EntityInterface
|
||||
* A new entity object.
|
||||
*
|
||||
* @deprecated in Drupal 8.x-dev, will be removed before Drupal 9.0.0. Use
|
||||
* the <EntityType>::create($values) method if the entity type is known or
|
||||
* \Drupal::entityManager()->getStorage($entity_type)->create($values) if the
|
||||
* entity type is variable.
|
||||
*/
|
||||
function entity_create($entity_type, array $values = array()) {
|
||||
return \Drupal::entityManager()
|
||||
->getStorage($entity_type)
|
||||
->create($values);
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the label of an entity.
|
||||
*
|
||||
* This is a wrapper for Drupal\Core\Entity\EntityInterface::label(). This function
|
||||
* should only be used as a callback, e.g. for menu title callbacks.
|
||||
*
|
||||
* @param \Drupal\Core\Entity\EntityInterface $entity
|
||||
* The entity for which to generate the label.
|
||||
* @param $langcode
|
||||
* (optional) The language code of the language that should be used for
|
||||
* getting the label. If set to NULL, the entity's default language is
|
||||
* used.
|
||||
*
|
||||
* @return
|
||||
* The label of the entity, or NULL if there is no label defined.
|
||||
*
|
||||
* @see \Drupal\Core\Entity\EntityInterface::label()
|
||||
*/
|
||||
function entity_page_label(EntityInterface $entity, $langcode = NULL) {
|
||||
return $entity->label($langcode);
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the render array for an entity.
|
||||
*
|
||||
* @param \Drupal\Core\Entity\EntityInterface $entity
|
||||
* The entity to be rendered.
|
||||
* @param string $view_mode
|
||||
* The view mode that should be used to display the entity.
|
||||
* @param string $langcode
|
||||
* (optional) For which language the entity should be rendered, defaults to
|
||||
* the current content language.
|
||||
* @param bool $reset
|
||||
* (optional) Whether to reset the render cache for the requested entity.
|
||||
* Defaults to FALSE.
|
||||
*
|
||||
* @return array
|
||||
* A render array for the entity.
|
||||
*/
|
||||
function entity_view(EntityInterface $entity, $view_mode, $langcode = NULL, $reset = FALSE) {
|
||||
$render_controller = \Drupal::entityManager()->getViewBuilder($entity->getEntityTypeId());
|
||||
if ($reset) {
|
||||
$render_controller->resetCache(array($entity->id()));
|
||||
}
|
||||
return $render_controller->view($entity, $view_mode, $langcode);
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the render array for the provided entities.
|
||||
*
|
||||
* @param \Drupal\Core\Entity\EntityInterface[] $entities
|
||||
* The entities to be rendered, must be of the same type.
|
||||
* @param string $view_mode
|
||||
* The view mode that should be used to display the entity.
|
||||
* @param string $langcode
|
||||
* (optional) For which language the entity should be rendered, defaults to
|
||||
* the current content language.
|
||||
* @param bool $reset
|
||||
* (optional) Whether to reset the render cache for the requested entities.
|
||||
* Defaults to FALSE.
|
||||
*
|
||||
* @return array
|
||||
* A render array for the entities, indexed by the same keys as the
|
||||
* entities array passed in $entities.
|
||||
*/
|
||||
function entity_view_multiple(array $entities, $view_mode, $langcode = NULL, $reset = FALSE) {
|
||||
$render_controller = \Drupal::entityManager()->getViewBuilder(reset($entities)->getEntityTypeId());
|
||||
if ($reset) {
|
||||
$render_controller->resetCache(array_keys($entities));
|
||||
}
|
||||
return $render_controller->viewMultiple($entities, $view_mode, $langcode);
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the entity view display associated to a bundle and view mode.
|
||||
*
|
||||
* Use this function when assigning suggested display options for a component
|
||||
* in a given view mode. Note that they will only be actually used at render
|
||||
* time if the view mode itself is configured to use dedicated display settings
|
||||
* for the bundle; if not, the 'default' display is used instead.
|
||||
*
|
||||
* The function reads the entity view display from the current configuration, or
|
||||
* returns a ready-to-use empty one if configuration entry exists yet for this
|
||||
* bundle and view mode. This streamlines manipulation of display objects by
|
||||
* always returning a consistent object that reflects the current state of the
|
||||
* configuration.
|
||||
*
|
||||
* Example usage:
|
||||
* - Set the 'body' field to be displayed and the 'field_image' field to be
|
||||
* hidden on article nodes in the 'default' display.
|
||||
* @code
|
||||
* entity_get_display('node', 'article', 'default')
|
||||
* ->setComponent('body', array(
|
||||
* 'type' => 'text_summary_or_trimmed',
|
||||
* 'settings' => array('trim_length' => '200')
|
||||
* 'weight' => 1,
|
||||
* ))
|
||||
* ->removeComponent('field_image')
|
||||
* ->save();
|
||||
* @endcode
|
||||
*
|
||||
* @param string $entity_type
|
||||
* The entity type.
|
||||
* @param string $bundle
|
||||
* The bundle.
|
||||
* @param string $view_mode
|
||||
* The view mode, or 'default' to retrieve the 'default' display object for
|
||||
* this bundle.
|
||||
*
|
||||
* @return \Drupal\Core\Entity\Display\EntityViewDisplayInterface
|
||||
* The entity view display associated to the view mode.
|
||||
*/
|
||||
function entity_get_display($entity_type, $bundle, $view_mode) {
|
||||
// Try loading the display from configuration.
|
||||
$display = entity_load('entity_view_display', $entity_type . '.' . $bundle . '.' . $view_mode);
|
||||
|
||||
// If not found, create a fresh display object. We do not preemptively create
|
||||
// new entity_view_display configuration entries for each existing entity type
|
||||
// and bundle whenever a new view mode becomes available. Instead,
|
||||
// configuration entries are only created when a display object is explicitly
|
||||
// configured and saved.
|
||||
if (!$display) {
|
||||
$display = entity_create('entity_view_display', array(
|
||||
'targetEntityType' => $entity_type,
|
||||
'bundle' => $bundle,
|
||||
'mode' => $view_mode,
|
||||
'status' => TRUE,
|
||||
));
|
||||
}
|
||||
|
||||
return $display;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the entity form display associated to a bundle and form mode.
|
||||
*
|
||||
* The function reads the entity form display object from the current
|
||||
* configuration, or returns a ready-to-use empty one if no configuration entry
|
||||
* exists yet for this bundle and form mode. This streamlines manipulation of
|
||||
* entity form displays by always returning a consistent object that reflects
|
||||
* the current state of the configuration.
|
||||
*
|
||||
* Example usage:
|
||||
* - Set the 'body' field to be displayed with the 'text_textarea_with_summary'
|
||||
* widget and the 'field_image' field to be hidden on article nodes in the
|
||||
* 'default' form mode.
|
||||
* @code
|
||||
* entity_get_form_display('node', 'article', 'default')
|
||||
* ->setComponent('body', array(
|
||||
* 'type' => 'text_textarea_with_summary',
|
||||
* 'weight' => 1,
|
||||
* ))
|
||||
* ->setComponent('field_image', array(
|
||||
* 'type' => 'hidden',
|
||||
* ))
|
||||
* ->save();
|
||||
* @endcode
|
||||
*
|
||||
* @param string $entity_type
|
||||
* The entity type.
|
||||
* @param string $bundle
|
||||
* The bundle.
|
||||
* @param string $form_mode
|
||||
* The form mode.
|
||||
*
|
||||
* @return \Drupal\Core\Entity\Display\EntityFormDisplayInterface
|
||||
* The entity form display associated to the given form mode.
|
||||
*/
|
||||
function entity_get_form_display($entity_type, $bundle, $form_mode) {
|
||||
// Try loading the entity from configuration.
|
||||
$entity_form_display = entity_load('entity_form_display', $entity_type . '.' . $bundle . '.' . $form_mode);
|
||||
|
||||
// If not found, create a fresh entity object. We do not preemptively create
|
||||
// new entity form display configuration entries for each existing entity type
|
||||
// and bundle whenever a new form mode becomes available. Instead,
|
||||
// configuration entries are only created when an entity form display is
|
||||
// explicitly configured and saved.
|
||||
if (!$entity_form_display) {
|
||||
$entity_form_display = entity_create('entity_form_display', array(
|
||||
'targetEntityType' => $entity_type,
|
||||
'bundle' => $bundle,
|
||||
'mode' => $form_mode,
|
||||
'status' => TRUE,
|
||||
));
|
||||
}
|
||||
|
||||
return $entity_form_display;
|
||||
}
|
287
core/includes/errors.inc
Normal file
287
core/includes/errors.inc
Normal file
|
@ -0,0 +1,287 @@
|
|||
<?php
|
||||
|
||||
/**
|
||||
* @file
|
||||
* Functions for error handling.
|
||||
*/
|
||||
|
||||
use Drupal\Component\Utility\SafeMarkup;
|
||||
use Drupal\Component\Utility\Xss;
|
||||
use Drupal\Core\Logger\RfcLogLevel;
|
||||
use Drupal\Core\Utility\Error;
|
||||
|
||||
/**
|
||||
* 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() {
|
||||
$types = array(
|
||||
E_ERROR => array('Error', RfcLogLevel::ERROR),
|
||||
E_WARNING => array('Warning', RfcLogLevel::WARNING),
|
||||
E_PARSE => array('Parse error', RfcLogLevel::ERROR),
|
||||
E_NOTICE => array('Notice', RfcLogLevel::NOTICE),
|
||||
E_CORE_ERROR => array('Core error', RfcLogLevel::ERROR),
|
||||
E_CORE_WARNING => array('Core warning', RfcLogLevel::WARNING),
|
||||
E_COMPILE_ERROR => array('Compile error', RfcLogLevel::ERROR),
|
||||
E_COMPILE_WARNING => array('Compile warning', RfcLogLevel::WARNING),
|
||||
E_USER_ERROR => array('User error', RfcLogLevel::ERROR),
|
||||
E_USER_WARNING => array('User warning', RfcLogLevel::WARNING),
|
||||
E_USER_NOTICE => array('User notice', RfcLogLevel::NOTICE),
|
||||
E_STRICT => array('Strict warning', RfcLogLevel::DEBUG),
|
||||
E_RECOVERABLE_ERROR => array('Recoverable fatal error', RfcLogLevel::ERROR),
|
||||
E_DEPRECATED => array('Deprecated function', RfcLogLevel::DEBUG),
|
||||
E_USER_DEPRECATED => array('User deprecated function', RfcLogLevel::DEBUG),
|
||||
);
|
||||
|
||||
return $types;
|
||||
}
|
||||
|
||||
/**
|
||||
* Provides custom PHP error handling.
|
||||
*
|
||||
* @param $error_level
|
||||
* The level of the error raised.
|
||||
* @param $message
|
||||
* The error message.
|
||||
* @param $filename
|
||||
* The filename that the error was raised in.
|
||||
* @param $line
|
||||
* The line number the error was raised at.
|
||||
* @param $context
|
||||
* An array that points to the active symbol table at the point the error
|
||||
* occurred.
|
||||
*/
|
||||
function _drupal_error_handler_real($error_level, $message, $filename, $line, $context) {
|
||||
if ($error_level & error_reporting()) {
|
||||
$types = drupal_error_levels();
|
||||
list($severity_msg, $severity_level) = $types[$error_level];
|
||||
$backtrace = debug_backtrace();
|
||||
$caller = Error::getLastCaller($backtrace);
|
||||
|
||||
// We treat recoverable errors as fatal.
|
||||
_drupal_log_error(array(
|
||||
'%type' => isset($types[$error_level]) ? $severity_msg : 'Unknown error',
|
||||
// The standard PHP error handler considers that the error messages
|
||||
// are HTML. We mimick this behavior here.
|
||||
'!message' => Xss::filterAdmin($message),
|
||||
'%function' => $caller['function'],
|
||||
'%file' => $caller['file'],
|
||||
'%line' => $caller['line'],
|
||||
'severity_level' => $severity_level,
|
||||
'backtrace' => $backtrace,
|
||||
), $error_level == E_RECOVERABLE_ERROR);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 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 $error
|
||||
* Optional error to examine for ERROR_REPORTING_DISPLAY_SOME.
|
||||
*
|
||||
* @return
|
||||
* 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 $error
|
||||
* An array with the following keys: %type, !message, %function, %file,
|
||||
* %line, severity_level, and backtrace. All the parameters are plain-text,
|
||||
* with the exception of !message, which needs to be a safe HTML string, and
|
||||
* backtrace, which is a standard PHP backtrace.
|
||||
* @param $fatal
|
||||
* TRUE if the error is fatal.
|
||||
*/
|
||||
function _drupal_log_error($error, $fatal = FALSE) {
|
||||
$is_installer = drupal_installation_attempted();
|
||||
// Initialize a maintenance theme if the bootstrap was not complete.
|
||||
// Do it early because drupal_set_message() triggers a
|
||||
// \Drupal\Core\Theme\ThemeManager::initTheme().
|
||||
if ($fatal && \Drupal::hasService('theme.manager')) {
|
||||
// The installer initializes a maintenance theme at the earliest possible
|
||||
// point in time already. Do not unset that.
|
||||
if (!$is_installer) {
|
||||
\Drupal::theme()->resetActiveTheme();
|
||||
}
|
||||
if (!defined('MAINTENANCE_MODE')) {
|
||||
define('MAINTENANCE_MODE', 'error');
|
||||
}
|
||||
// No-op if the active theme is set already.
|
||||
drupal_maintenance_theme();
|
||||
}
|
||||
|
||||
// Backtrace array is not a valid replacement value for t().
|
||||
$backtrace = $error['backtrace'];
|
||||
unset($error['backtrace']);
|
||||
|
||||
// When running inside the testing framework, we relay the errors
|
||||
// to the tested site by the way of HTTP headers.
|
||||
if (DRUPAL_TEST_IN_CHILD_SITE && !headers_sent() && (!defined('SIMPLETEST_COLLECT_ERRORS') || SIMPLETEST_COLLECT_ERRORS)) {
|
||||
// $number does not use drupal_static as it should not be reset
|
||||
// as it uniquely identifies each PHP error.
|
||||
static $number = 0;
|
||||
$assertion = array(
|
||||
$error['!message'],
|
||||
$error['%type'],
|
||||
array(
|
||||
'function' => $error['%function'],
|
||||
'file' => $error['%file'],
|
||||
'line' => $error['%line'],
|
||||
),
|
||||
);
|
||||
header('X-Drupal-Assertion-' . $number . ': ' . rawurlencode(serialize($assertion)));
|
||||
$number++;
|
||||
}
|
||||
|
||||
// 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')) {
|
||||
\Drupal::logger('php')->log($error['severity_level'], '%type: !message in %function (line %line of %file).', $error);
|
||||
}
|
||||
|
||||
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.
|
||||
print html_entity_decode(strip_tags(format_string('%type: !message in %function (line %line of %file).', $error))). "\n";
|
||||
exit;
|
||||
}
|
||||
}
|
||||
|
||||
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.
|
||||
print format_string('%type: !message in %function (line %line of %file).', $error);
|
||||
}
|
||||
exit;
|
||||
}
|
||||
}
|
||||
else {
|
||||
// Display the message if the current error reporting level allows this type
|
||||
// of message to be displayed, and unconditionally in update.php.
|
||||
if (error_displayable($error)) {
|
||||
$class = 'error';
|
||||
|
||||
// If error type is 'User notice' then treat it as debug information
|
||||
// instead of an error message.
|
||||
// @see debug()
|
||||
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 does not happen for (false) security.
|
||||
if (\Drupal::hasService('app.root')) {
|
||||
$root_length = strlen(\Drupal::root());
|
||||
if (substr($error['%file'], 0, $root_length) == \Drupal::root()) {
|
||||
$error['%file'] = substr($error['%file'], $root_length + 1);
|
||||
}
|
||||
}
|
||||
// Should not translate the string to avoid errors producing more errors.
|
||||
$message = format_string('%type: !message in %function (line %line of %file).', $error);
|
||||
|
||||
// Check if verbose error reporting is on.
|
||||
$error_level = _drupal_get_error_level();
|
||||
|
||||
if ($error_level == ERROR_REPORTING_DISPLAY_VERBOSE) {
|
||||
// First trace is the error itself, already contained in the message.
|
||||
// While the second trace is the error source and also contained in the
|
||||
// message, the message doesn't contain argument values, so we output it
|
||||
// once more in the backtrace.
|
||||
array_shift($backtrace);
|
||||
// Generate a backtrace containing only scalar argument values.
|
||||
$message .= '<pre class="backtrace">' . Error::formatBacktrace($backtrace) . '</pre>';
|
||||
}
|
||||
if (\Drupal::hasService('session')) {
|
||||
// Message display is dependent on sessions being available.
|
||||
drupal_set_message(SafeMarkup::set($message), $class, TRUE);
|
||||
}
|
||||
else {
|
||||
print $message;
|
||||
}
|
||||
}
|
||||
|
||||
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. Please try again later.';
|
||||
if ($is_installer) {
|
||||
// install_display_output() prints the output and ends script execution.
|
||||
$output = array(
|
||||
'#title' => 'Error',
|
||||
'#markup' => $message,
|
||||
);
|
||||
install_display_output($output, $GLOBALS['install_state']);
|
||||
exit;
|
||||
}
|
||||
|
||||
$bare_html_page_renderer = \Drupal::service('bare_html_page_renderer');
|
||||
$response = $bare_html_page_renderer->renderBarePage(['#markup' => $message], 'Error', 'maintenance_page');
|
||||
$response->setStatusCode(500, '500 Service unavailable (with message)');
|
||||
// An exception must halt script execution.
|
||||
$response->send();
|
||||
exit;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 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 (drupal_installation_attempted()) {
|
||||
return ERROR_REPORTING_DISPLAY_VERBOSE;
|
||||
}
|
||||
$error_level = NULL;
|
||||
if (\Drupal::hasService('config.factory')) {
|
||||
$error_level = \Drupal::config('system.logging')->get('error_level');
|
||||
}
|
||||
// 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;
|
||||
}
|
1261
core/includes/file.inc
Normal file
1261
core/includes/file.inc
Normal file
File diff suppressed because it is too large
Load diff
934
core/includes/form.inc
Normal file
934
core/includes/form.inc
Normal file
|
@ -0,0 +1,934 @@
|
|||
<?php
|
||||
|
||||
/**
|
||||
* @file
|
||||
* Functions for form and batch generation and processing.
|
||||
*/
|
||||
|
||||
use Drupal\Component\Utility\NestedArray;
|
||||
use Drupal\Component\Utility\SafeMarkup;
|
||||
use Drupal\Component\Utility\UrlHelper;
|
||||
use Drupal\Component\Utility\Xss;
|
||||
use Drupal\Core\Database\Database;
|
||||
use Drupal\Core\Form\FormStateInterface;
|
||||
use Drupal\Core\Form\FormElementHelper;
|
||||
use Drupal\Core\Form\OptGroup;
|
||||
use Drupal\Core\Render\Element;
|
||||
use Drupal\Core\Template\Attribute;
|
||||
use Drupal\Core\Url;
|
||||
use Symfony\Component\HttpFoundation\RedirectResponse;
|
||||
|
||||
/**
|
||||
* Prepares variables for select element templates.
|
||||
*
|
||||
* Default template: select.html.twig.
|
||||
*
|
||||
* It is possible to group options together; to do this, change the format of
|
||||
* $options to an associative array in which the keys are group labels, and the
|
||||
* values are associative arrays in the normal $options format.
|
||||
*
|
||||
* @param $variables
|
||||
* An associative array containing:
|
||||
* - element: An associative array containing the properties of the element.
|
||||
* Properties used: #title, #value, #options, #description, #extra,
|
||||
* #multiple, #required, #name, #attributes, #size.
|
||||
*/
|
||||
function template_preprocess_select(&$variables) {
|
||||
$element = $variables['element'];
|
||||
Element::setAttributes($element, array('id', 'name', 'size'));
|
||||
Element\RenderElement::setAttributes($element, array('form-select'));
|
||||
|
||||
$variables['attributes'] = $element['#attributes'];
|
||||
$variables['options'] = form_select_options($element);
|
||||
}
|
||||
|
||||
/**
|
||||
* Converts an array of options into HTML, for use in select list form elements.
|
||||
*
|
||||
* This function calls itself recursively to obtain the values for each optgroup
|
||||
* within the list of options and when the function encounters an object with
|
||||
* an 'options' property inside $element['#options'].
|
||||
*
|
||||
* @param array $element
|
||||
* An associative array containing the following key-value pairs:
|
||||
* - #multiple: Optional Boolean indicating if the user may select more than
|
||||
* one item.
|
||||
* - #options: An associative array of options to render as HTML. Each array
|
||||
* value can be a string, an array, or an object with an 'option' property:
|
||||
* - A string or integer key whose value is a translated string is
|
||||
* interpreted as a single HTML option element. Do not use placeholders
|
||||
* that sanitize data: doing so will lead to double-escaping. Note that
|
||||
* the key will be visible in the HTML and could be modified by malicious
|
||||
* users, so don't put sensitive information in it.
|
||||
* - A translated string key whose value is an array indicates a group of
|
||||
* options. The translated string is used as the label attribute for the
|
||||
* optgroup. Do not use placeholders to sanitize data: doing so will lead
|
||||
* to double-escaping. The array should contain the options you wish to
|
||||
* group and should follow the syntax of $element['#options'].
|
||||
* - If the function encounters a string or integer key whose value is an
|
||||
* object with an 'option' property, the key is ignored, the contents of
|
||||
* the option property are interpreted as $element['#options'], and the
|
||||
* resulting HTML is added to the output.
|
||||
* - #value: Optional integer, string, or array representing which option(s)
|
||||
* to pre-select when the list is first displayed. The integer or string
|
||||
* must match the key of an option in the '#options' list. If '#multiple' is
|
||||
* TRUE, this can be an array of integers or strings.
|
||||
* @param array|null $choices
|
||||
* (optional) Either an associative array of options in the same format as
|
||||
* $element['#options'] above, or NULL. This parameter is only used internally
|
||||
* and is not intended to be passed in to the initial function call.
|
||||
*
|
||||
* @return string
|
||||
* An HTML string of options and optgroups for use in a select form element.
|
||||
*/
|
||||
function form_select_options($element, $choices = NULL) {
|
||||
if (!isset($choices)) {
|
||||
if (empty($element['#options'])) {
|
||||
return '';
|
||||
}
|
||||
$choices = $element['#options'];
|
||||
}
|
||||
// array_key_exists() accommodates the rare event where $element['#value'] is NULL.
|
||||
// isset() fails in this situation.
|
||||
$value_valid = isset($element['#value']) || array_key_exists('#value', $element);
|
||||
$value_is_array = $value_valid && is_array($element['#value']);
|
||||
// Check if the element is multiple select and no value has been selected.
|
||||
$empty_value = (empty($element['#value']) && !empty($element['#multiple']));
|
||||
$options = '';
|
||||
foreach ($choices as $key => $choice) {
|
||||
if (is_array($choice)) {
|
||||
$options .= '<optgroup label="' . SafeMarkup::checkPlain($key) . '">';
|
||||
$options .= form_select_options($element, $choice);
|
||||
$options .= '</optgroup>';
|
||||
}
|
||||
elseif (is_object($choice) && isset($choice->option)) {
|
||||
$options .= form_select_options($element, $choice->option);
|
||||
}
|
||||
else {
|
||||
$key = (string) $key;
|
||||
$empty_choice = $empty_value && $key == '_none';
|
||||
if ($value_valid && ((!$value_is_array && (string) $element['#value'] === $key || ($value_is_array && in_array($key, $element['#value']))) || $empty_choice)) {
|
||||
$selected = ' selected="selected"';
|
||||
}
|
||||
else {
|
||||
$selected = '';
|
||||
}
|
||||
$options .= '<option value="' . SafeMarkup::checkPlain($key) . '"' . $selected . '>' . SafeMarkup::checkPlain($choice) . '</option>';
|
||||
}
|
||||
}
|
||||
return SafeMarkup::set($options);
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the indexes of a select element's options matching a given key.
|
||||
*
|
||||
* This function is useful if you need to modify the options that are
|
||||
* already in a form element; for example, to remove choices which are
|
||||
* not valid because of additional filters imposed by another module.
|
||||
* One example might be altering the choices in a taxonomy selector.
|
||||
* To correctly handle the case of a multiple hierarchy taxonomy,
|
||||
* #options arrays can now hold an array of objects, instead of a
|
||||
* direct mapping of keys to labels, so that multiple choices in the
|
||||
* selector can have the same key (and label). This makes it difficult
|
||||
* to manipulate directly, which is why this helper function exists.
|
||||
*
|
||||
* This function does not support optgroups (when the elements of the
|
||||
* #options array are themselves arrays), and will return FALSE if
|
||||
* arrays are found. The caller must either flatten/restore or
|
||||
* manually do their manipulations in this case, since returning the
|
||||
* index is not sufficient, and supporting this would make the
|
||||
* "helper" too complicated and cumbersome to be of any help.
|
||||
*
|
||||
* As usual with functions that can return array() or FALSE, do not
|
||||
* forget to use === and !== if needed.
|
||||
*
|
||||
* @param $element
|
||||
* The select element to search.
|
||||
* @param $key
|
||||
* The key to look for.
|
||||
*
|
||||
* @return
|
||||
* An array of indexes that match the given $key. Array will be
|
||||
* empty if no elements were found. FALSE if optgroups were found.
|
||||
*/
|
||||
function form_get_options($element, $key) {
|
||||
$keys = array();
|
||||
foreach ($element['#options'] as $index => $choice) {
|
||||
if (is_array($choice)) {
|
||||
return FALSE;
|
||||
}
|
||||
elseif (is_object($choice)) {
|
||||
if (isset($choice->option[$key])) {
|
||||
$keys[] = $index;
|
||||
}
|
||||
}
|
||||
elseif ($index == $key) {
|
||||
$keys[] = $index;
|
||||
}
|
||||
}
|
||||
return $keys;
|
||||
}
|
||||
|
||||
/**
|
||||
* Prepares variables for fieldset element templates.
|
||||
*
|
||||
* Default template: fieldset.html.twig.
|
||||
*
|
||||
* @param array $variables
|
||||
* An associative array containing:
|
||||
* - element: An associative array containing the properties of the element.
|
||||
* Properties used: #attributes, #children, #description, #id, #title,
|
||||
* #value.
|
||||
*/
|
||||
function template_preprocess_fieldset(&$variables) {
|
||||
$element = $variables['element'];
|
||||
Element::setAttributes($element, array('id'));
|
||||
Element\RenderElement::setAttributes($element);
|
||||
$variables['attributes'] = $element['#attributes'];
|
||||
$variables['prefix'] = isset($element['#field_prefix']) ? $element['#field_prefix'] : NULL;
|
||||
$variables['suffix'] = isset($element['#field_suffix']) ? $element['#field_suffix'] : NULL;
|
||||
$variables['title_display'] = isset($element['#title_display']) ? $element['#title_display'] : NULL;
|
||||
$variables['children'] = $element['#children'];
|
||||
$variables['required'] = !empty($element['#required']) ? $element['#required'] : NULL;
|
||||
|
||||
$variables['legend']['title'] = (isset($element['#title']) && $element['#title'] !== '') ? Xss::filterAdmin($element['#title']) : '';
|
||||
$variables['legend']['attributes'] = new Attribute();
|
||||
$variables['legend_span']['attributes'] = new Attribute();
|
||||
|
||||
if (!empty($element['#description'])) {
|
||||
$description_id = $element['#attributes']['id'] . '--description';
|
||||
$description_attributes['id'] = $description_id;
|
||||
$variables['description']['attributes'] = new Attribute($description_attributes);
|
||||
$variables['description']['content'] = $element['#description'];
|
||||
|
||||
// Add the description's id to the fieldset aria attributes.
|
||||
$variables['attributes']['aria-describedby'] = $description_id;
|
||||
}
|
||||
|
||||
// Display any error messages.
|
||||
$variables['errors'] = NULL;
|
||||
if (!empty($element['#errors']) && empty($element['#error_no_message'])) {
|
||||
$variables['errors'] = $element['#errors'];
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Prepares variables for details element templates.
|
||||
*
|
||||
* Default template: details.html.twig.
|
||||
*
|
||||
* @param array $variables
|
||||
* An associative array containing:
|
||||
* - element: An associative array containing the properties of the element.
|
||||
* Properties used: #attributes, #children, #open,
|
||||
* #description, #id, #title, #value, #optional.
|
||||
*/
|
||||
function template_preprocess_details(&$variables) {
|
||||
$element = $variables['element'];
|
||||
$variables['attributes'] = $element['#attributes'];
|
||||
$variables['summary_attributes'] = new Attribute();
|
||||
if (!empty($element['#title'])) {
|
||||
$variables['summary_attributes']['role'] = 'button';
|
||||
if (!empty($element['#attributes']['id'])) {
|
||||
$variables['summary_attributes']['aria-controls'] = $element['#attributes']['id'];
|
||||
}
|
||||
$variables['summary_attributes']['aria-expanded'] = !empty($element['#attributes']['open']) ? 'true' : 'false';
|
||||
$variables['summary_attributes']['aria-pressed'] = $variables['summary_attributes']['aria-expanded'];
|
||||
}
|
||||
$variables['title'] = (!empty($element['#title'])) ? $element['#title'] : '';
|
||||
$variables['description'] = (!empty($element['#description'])) ? $element['#description'] : '';
|
||||
$variables['children'] = (isset($element['#children'])) ? $element['#children'] : '';
|
||||
$variables['value'] = (isset($element['#value'])) ? $element['#value'] : '';
|
||||
}
|
||||
|
||||
/**
|
||||
* Prepares variables for radios templates.
|
||||
*
|
||||
* Default template: radios.html.twig.
|
||||
*
|
||||
* @param array $variables
|
||||
* An associative array containing:
|
||||
* - element: An associative array containing the properties of the element.
|
||||
* Properties used: #title, #value, #options, #description, #required,
|
||||
* #attributes, #children.
|
||||
*/
|
||||
function template_preprocess_radios(&$variables) {
|
||||
$element = $variables['element'];
|
||||
$variables['attributes'] = array();
|
||||
if (isset($element['#id'])) {
|
||||
$variables['attributes']['id'] = $element['#id'];
|
||||
}
|
||||
if (isset($element['#attributes']['title'])) {
|
||||
$variables['attributes']['title'] = $element['#attributes']['title'];
|
||||
}
|
||||
$variables['children'] = $element['#children'];
|
||||
}
|
||||
|
||||
/**
|
||||
* Prepares variables for checkboxes templates.
|
||||
*
|
||||
* Default template: checkboxes.html.twig.
|
||||
*
|
||||
* @param array $variables
|
||||
* An associative array containing:
|
||||
* - element: An associative array containing the properties of the element.
|
||||
* Properties used: #children, #attributes.
|
||||
*/
|
||||
function template_preprocess_checkboxes(&$variables) {
|
||||
$element = $variables['element'];
|
||||
$variables['attributes'] = array();
|
||||
if (isset($element['#id'])) {
|
||||
$variables['attributes']['id'] = $element['#id'];
|
||||
}
|
||||
if (isset($element['#attributes']['title'])) {
|
||||
$variables['attributes']['title'] = $element['#attributes']['title'];
|
||||
}
|
||||
$variables['children'] = $element['#children'];
|
||||
}
|
||||
|
||||
/**
|
||||
* Prepares variables for vertical tabs templates.
|
||||
*
|
||||
* Default template: vertical-tabs.html.twig.
|
||||
*
|
||||
* @param array $variables
|
||||
* An associative array containing:
|
||||
* - element: An associative array containing the properties and children of
|
||||
* the details element. Properties used: #children.
|
||||
*
|
||||
*/
|
||||
function template_preprocess_vertical_tabs(&$variables) {
|
||||
$element = $variables['element'];
|
||||
$variables['children'] = (!empty($element['#children'])) ? $element['#children'] : '';
|
||||
}
|
||||
|
||||
/**
|
||||
* Prepares variables for input templates.
|
||||
*
|
||||
* Default template: input.html.twig.
|
||||
*
|
||||
* @param array $variables
|
||||
* An associative array containing:
|
||||
* - element: An associative array containing the properties of the element.
|
||||
* Properties used: #attributes.
|
||||
*/
|
||||
function template_preprocess_input(&$variables) {
|
||||
$element = $variables['element'];
|
||||
$variables['children'] = $element['#children'];
|
||||
}
|
||||
|
||||
/**
|
||||
* Prepares variables for form templates.
|
||||
*
|
||||
* Default template: form.html.twig.
|
||||
*
|
||||
* @param $variables
|
||||
* An associative array containing:
|
||||
* - element: An associative array containing the properties of the element.
|
||||
* Properties used: #action, #method, #attributes, #children
|
||||
*/
|
||||
function template_preprocess_form(&$variables) {
|
||||
$element = $variables['element'];
|
||||
if (isset($element['#action'])) {
|
||||
$element['#attributes']['action'] = UrlHelper::stripDangerousProtocols($element['#action']);
|
||||
}
|
||||
Element::setAttributes($element, array('method', 'id'));
|
||||
if (empty($element['#attributes']['accept-charset'])) {
|
||||
$element['#attributes']['accept-charset'] = "UTF-8";
|
||||
}
|
||||
$variables['attributes'] = $element['#attributes'];
|
||||
$variables['children'] = $element['#children'];
|
||||
}
|
||||
|
||||
/**
|
||||
* Prepares variables for textarea templates.
|
||||
*
|
||||
* Default template: textarea.html.twig.
|
||||
*
|
||||
* @param array $variables
|
||||
* An associative array containing:
|
||||
* - element: An associative array containing the properties of the element.
|
||||
* Properties used: #title, #value, #description, #rows, #cols,
|
||||
* #placeholder, #required, #attributes, #resizable
|
||||
*
|
||||
*/
|
||||
function template_preprocess_textarea(&$variables) {
|
||||
$element = $variables['element'];
|
||||
Element::setAttributes($element, array('id', 'name', 'rows', 'cols', 'placeholder'));
|
||||
Element\RenderElement::setAttributes($element, array('form-textarea'));
|
||||
$variables['wrapper_attributes'] = new Attribute();
|
||||
$variables['attributes'] = new Attribute($element['#attributes']);
|
||||
$variables['value'] = SafeMarkup::checkPlain($element['#value']);
|
||||
$variables['resizable'] = !empty($element['#resizable']) ? $element['#resizable'] : NULL;
|
||||
$variables['required'] = !empty($element['#required']) ? $element['#required'] : NULL;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns HTML for a form element.
|
||||
* Prepares variables for form element templates.
|
||||
*
|
||||
* Default template: form-element.html.twig.
|
||||
*
|
||||
* In addition to the element itself, the DIV contains a label for the element
|
||||
* based on the optional #title_display property, and an optional #description.
|
||||
*
|
||||
* The optional #title_display property can have these values:
|
||||
* - before: The label is output before the element. This is the default.
|
||||
* The label includes the #title and the required marker, if #required.
|
||||
* - after: The label is output after the element. For example, this is used
|
||||
* for radio and checkbox #type elements. If the #title is empty but the field
|
||||
* is #required, the label will contain only the required marker.
|
||||
* - invisible: Labels are critical for screen readers to enable them to
|
||||
* properly navigate through forms but can be visually distracting. This
|
||||
* property hides the label for everyone except screen readers.
|
||||
* - attribute: Set the title attribute on the element to create a tooltip
|
||||
* but output no label element. This is supported only for checkboxes
|
||||
* and radios in
|
||||
* \Drupal\Core\Render\Element\CompositeFormElementTrait::preRenderCompositeFormElement().
|
||||
* It is used where a visual label is not needed, such as a table of
|
||||
* checkboxes where the row and column provide the context. The tooltip will
|
||||
* include the title and required marker.
|
||||
*
|
||||
* If the #title property is not set, then the label and any required marker
|
||||
* will not be output, regardless of the #title_display or #required values.
|
||||
* This can be useful in cases such as the password_confirm element, which
|
||||
* creates children elements that have their own labels and required markers,
|
||||
* but the parent element should have neither. Use this carefully because a
|
||||
* field without an associated label can cause accessibility challenges.
|
||||
*
|
||||
* @param array $variables
|
||||
* An associative array containing:
|
||||
* - element: An associative array containing the properties of the element.
|
||||
* Properties used: #title, #title_display, #description, #id, #required,
|
||||
* #children, #type, #name.
|
||||
*/
|
||||
function template_preprocess_form_element(&$variables) {
|
||||
$element = &$variables['element'];
|
||||
|
||||
// This function is invoked as theme wrapper, but the rendered form element
|
||||
// may not necessarily have been processed by
|
||||
// \Drupal::formBuilder()->doBuildForm().
|
||||
$element += array(
|
||||
'#title_display' => 'before',
|
||||
'#wrapper_attributes' => array(),
|
||||
);
|
||||
$variables['attributes'] = $element['#wrapper_attributes'];
|
||||
|
||||
// Add element #id for #type 'item' and 'password_confirm'.
|
||||
if (isset($element['#markup']) && !empty($element['#id'])) {
|
||||
$variables['attributes']['id'] = $element['#id'];
|
||||
}
|
||||
|
||||
// Pass elements #type and #name to template.
|
||||
if (!empty($element['#type'])) {
|
||||
$variables['type'] = $element['#type'];
|
||||
}
|
||||
if (!empty($element['#name'])) {
|
||||
$variables['name'] = $element['#name'];
|
||||
}
|
||||
|
||||
// Pass elements disabled status to template.
|
||||
$variables['disabled'] = !empty($element['#attributes']['disabled']) ? $element['#attributes']['disabled'] : NULL;
|
||||
|
||||
// Display any error messages.
|
||||
$variables['errors'] = NULL;
|
||||
if (!empty($element['#errors']) && empty($element['#error_no_message'])) {
|
||||
$variables['errors'] = $element['#errors'];
|
||||
}
|
||||
|
||||
// If #title is not set, we don't display any label.
|
||||
if (!isset($element['#title'])) {
|
||||
$element['#title_display'] = 'none';
|
||||
}
|
||||
|
||||
$variables['title_display'] = $element['#title_display'];
|
||||
|
||||
$variables['prefix'] = isset($element['#field_prefix']) ? $element['#field_prefix'] : NULL;
|
||||
$variables['suffix'] = isset($element['#field_suffix']) ? $element['#field_suffix'] : NULL;
|
||||
|
||||
$variables['description'] = NULL;
|
||||
if (!empty($element['#description'])) {
|
||||
$variables['description_display'] = $element['#description_display'];
|
||||
$description_attributes = [];
|
||||
if (!empty($element['#id'])) {
|
||||
$description_attributes['id'] = $element['#id'] . '--description';
|
||||
}
|
||||
$variables['description']['attributes'] = new Attribute($description_attributes);
|
||||
$variables['description']['content'] = $element['#description'];
|
||||
}
|
||||
|
||||
// Add label_display and label variables to template.
|
||||
$variables['label_display'] = $element['#title_display'];
|
||||
$variables['label'] = array('#theme' => 'form_element_label');
|
||||
$variables['label'] += array_intersect_key($element, array_flip(array('#id', '#required', '#title', '#title_display')));
|
||||
|
||||
$variables['children'] = $element['#children'];
|
||||
}
|
||||
|
||||
/**
|
||||
* Prepares variables for form label templates.
|
||||
*
|
||||
* Form element labels include the #title and a #required marker. The label is
|
||||
* associated with the element itself by the element #id. Labels may appear
|
||||
* before or after elements, depending on form-element.html.twig and
|
||||
* #title_display.
|
||||
*
|
||||
* This function will not be called for elements with no labels, depending on
|
||||
* #title_display. For elements that have an empty #title and are not required,
|
||||
* this function will output no label (''). For required elements that have an
|
||||
* empty #title, this will output the required marker alone within the label.
|
||||
* The label will use the #id to associate the marker with the field that is
|
||||
* required. That is especially important for screenreader users to know
|
||||
* which field is required.
|
||||
*
|
||||
* @param array $variables
|
||||
* An associative array containing:
|
||||
* - element: An associative array containing the properties of the element.
|
||||
* Properties used: #required, #title, #id, #value, #description.
|
||||
*/
|
||||
function template_preprocess_form_element_label(&$variables) {
|
||||
$element = $variables['element'];
|
||||
// If title and required marker are both empty, output no label.
|
||||
$variables['title'] = (isset($element['#title']) && $element['#title'] !== '') ? Xss::filterAdmin($element['#title']) : '';
|
||||
$variables['attributes'] = array();
|
||||
|
||||
// Pass elements title_display to template.
|
||||
$variables['title_display'] = $element['#title_display'];
|
||||
|
||||
// A #for property of a dedicated #type 'label' element as precedence.
|
||||
if (!empty($element['#for'])) {
|
||||
$variables['attributes']['for'] = $element['#for'];
|
||||
// A custom #id allows the referenced form input element to refer back to
|
||||
// the label element; e.g., in the 'aria-labelledby' attribute.
|
||||
if (!empty($element['#id'])) {
|
||||
$variables['attributes']['id'] = $element['#id'];
|
||||
}
|
||||
}
|
||||
// Otherwise, point to the #id of the form input element.
|
||||
elseif (!empty($element['#id'])) {
|
||||
$variables['attributes']['for'] = $element['#id'];
|
||||
}
|
||||
|
||||
// Pass elements required to template.
|
||||
$variables['required'] = !empty($element['#required']) ? $element['#required'] : NULL;
|
||||
}
|
||||
|
||||
/**
|
||||
* @defgroup batch Batch operations
|
||||
* @{
|
||||
* Creates and processes batch operations.
|
||||
*
|
||||
* Functions allowing forms processing to be spread out over several page
|
||||
* requests, thus ensuring that the processing does not get interrupted
|
||||
* because of a PHP timeout, while allowing the user to receive feedback
|
||||
* on the progress of the ongoing operations.
|
||||
*
|
||||
* The API is primarily designed to integrate nicely with the Form API
|
||||
* workflow, but can also be used by non-Form API scripts (like update.php)
|
||||
* or even simple page callbacks (which should probably be used sparingly).
|
||||
*
|
||||
* Example:
|
||||
* @code
|
||||
* $batch = array(
|
||||
* 'title' => t('Exporting'),
|
||||
* 'operations' => array(
|
||||
* array('my_function_1', array($account->id(), 'story')),
|
||||
* array('my_function_2', array()),
|
||||
* ),
|
||||
* 'finished' => 'my_finished_callback',
|
||||
* 'file' => 'path_to_file_containing_myfunctions',
|
||||
* );
|
||||
* batch_set($batch);
|
||||
* // Only needed if not inside a form _submit handler.
|
||||
* // Setting redirect in batch_process.
|
||||
* batch_process('node/1');
|
||||
* @endcode
|
||||
*
|
||||
* Note: if the batch 'title', 'init_message', 'progress_message', or
|
||||
* 'error_message' could contain any user input, it is the responsibility of
|
||||
* the code calling batch_set() to sanitize them first with a function like
|
||||
* \Drupal\Component\Utility\SafeMarkup::checkPlain() or
|
||||
* \Drupal\Component\Utility\Xss::filter(). Furthermore, if the batch operation
|
||||
* returns any user input in the 'results' or 'message' keys of $context, it
|
||||
* must also sanitize them first.
|
||||
*
|
||||
* Sample callback_batch_operation():
|
||||
* @code
|
||||
* // Simple and artificial: load a node of a given type for a given user
|
||||
* function my_function_1($uid, $type, &$context) {
|
||||
* // The $context array gathers batch context information about the execution (read),
|
||||
* // as well as 'return values' for the current operation (write)
|
||||
* // The following keys are provided :
|
||||
* // 'results' (read / write): The array of results gathered so far by
|
||||
* // the batch processing, for the current operation to append its own.
|
||||
* // 'message' (write): A text message displayed in the progress page.
|
||||
* // The following keys allow for multi-step operations :
|
||||
* // 'sandbox' (read / write): An array that can be freely used to
|
||||
* // store persistent data between iterations. It is recommended to
|
||||
* // use this instead of $_SESSION, which is unsafe if the user
|
||||
* // continues browsing in a separate window while the batch is processing.
|
||||
* // 'finished' (write): A float number between 0 and 1 informing
|
||||
* // the processing engine of the completion level for the operation.
|
||||
* // 1 (or no value explicitly set) means the operation is finished
|
||||
* // and the batch processing can continue to the next operation.
|
||||
*
|
||||
* $nodes = entity_load_multiple_by_properties('node', array('uid' => $uid, 'type' => $type));
|
||||
* $node = reset($nodes);
|
||||
* $context['results'][] = $node->id() . ' : ' . SafeMarkup::checkPlain($node->label());
|
||||
* $context['message'] = SafeMarkup::checkPlain($node->label());
|
||||
* }
|
||||
*
|
||||
* // A more advanced example is a multi-step operation that loads all rows,
|
||||
* // five by five.
|
||||
* function my_function_2(&$context) {
|
||||
* if (empty($context['sandbox'])) {
|
||||
* $context['sandbox']['progress'] = 0;
|
||||
* $context['sandbox']['current_id'] = 0;
|
||||
* $context['sandbox']['max'] = db_query('SELECT COUNT(DISTINCT id) FROM {example}')->fetchField();
|
||||
* }
|
||||
* $limit = 5;
|
||||
* $result = db_select('example')
|
||||
* ->fields('example', array('id'))
|
||||
* ->condition('id', $context['sandbox']['current_id'], '>')
|
||||
* ->orderBy('id')
|
||||
* ->range(0, $limit)
|
||||
* ->execute();
|
||||
* foreach ($result as $row) {
|
||||
* $context['results'][] = $row->id . ' : ' . SafeMarkup::checkPlain($row->title);
|
||||
* $context['sandbox']['progress']++;
|
||||
* $context['sandbox']['current_id'] = $row->id;
|
||||
* $context['message'] = SafeMarkup::checkPlain($row->title);
|
||||
* }
|
||||
* if ($context['sandbox']['progress'] != $context['sandbox']['max']) {
|
||||
* $context['finished'] = $context['sandbox']['progress'] / $context['sandbox']['max'];
|
||||
* }
|
||||
* }
|
||||
* @endcode
|
||||
*
|
||||
* Sample callback_batch_finished():
|
||||
* @code
|
||||
* function my_finished_callback($success, $results, $operations) {
|
||||
* // The 'success' parameter means no fatal PHP errors were detected. All
|
||||
* // other error management should be handled using 'results'.
|
||||
* if ($success) {
|
||||
* $message = \Drupal::translation()->formatPlural(count($results), 'One post processed.', '@count posts processed.');
|
||||
* }
|
||||
* else {
|
||||
* $message = t('Finished with an error.');
|
||||
* }
|
||||
* drupal_set_message($message);
|
||||
* // Providing data for the redirected page is done through $_SESSION.
|
||||
* foreach ($results as $result) {
|
||||
* $items[] = t('Loaded node %title.', array('%title' => $result));
|
||||
* }
|
||||
* $_SESSION['my_batch_results'] = $items;
|
||||
* }
|
||||
* @endcode
|
||||
*/
|
||||
|
||||
/**
|
||||
* Adds a new batch.
|
||||
*
|
||||
* Batch operations are added as new batch sets. Batch sets are used to spread
|
||||
* processing (primarily, but not exclusively, forms processing) over several
|
||||
* page requests. This helps to ensure that the processing is not interrupted
|
||||
* due to PHP timeouts, while users are still able to receive feedback on the
|
||||
* progress of the ongoing operations. Combining related operations into
|
||||
* distinct batch sets provides clean code independence for each batch set,
|
||||
* ensuring that two or more batches, submitted independently, can be processed
|
||||
* without mutual interference. Each batch set may specify its own set of
|
||||
* operations and results, produce its own UI messages, and trigger its own
|
||||
* 'finished' callback. Batch sets are processed sequentially, with the progress
|
||||
* bar starting afresh for each new set.
|
||||
*
|
||||
* @param $batch_definition
|
||||
* An associative array defining the batch, with the following elements (all
|
||||
* are optional except as noted):
|
||||
* - operations: (required) Array of operations to be performed, where each
|
||||
* item is an array consisting of the name of an implementation of
|
||||
* callback_batch_operation() and an array of parameter.
|
||||
* Example:
|
||||
* @code
|
||||
* array(
|
||||
* array('callback_batch_operation_1', array($arg1)),
|
||||
* array('callback_batch_operation_2', array($arg2_1, $arg2_2)),
|
||||
* )
|
||||
* @endcode
|
||||
* - title: A safe, translated string to use as the title for the progress
|
||||
* page. Defaults to t('Processing').
|
||||
* - init_message: Message displayed while the processing is initialized.
|
||||
* Defaults to t('Initializing.').
|
||||
* - progress_message: Message displayed while processing the batch. Available
|
||||
* placeholders are @current, @remaining, @total, @percentage, @estimate and
|
||||
* @elapsed. Defaults to t('Completed @current of @total.').
|
||||
* - error_message: Message displayed if an error occurred while processing
|
||||
* the batch. Defaults to t('An error has occurred.').
|
||||
* - finished: Name of an implementation of callback_batch_finished(). This is
|
||||
* executed after the batch has completed. This should be used to perform
|
||||
* any result massaging that may be needed, and possibly save data in
|
||||
* $_SESSION for display after final page redirection.
|
||||
* - file: Path to the file containing the definitions of the 'operations' and
|
||||
* 'finished' functions, for instance if they don't reside in the main
|
||||
* .module file. The path should be relative to base_path(), and thus should
|
||||
* be built using drupal_get_path().
|
||||
* - css: Array of paths to CSS files to be used on the progress page.
|
||||
* - url_options: options passed to url() when constructing redirect URLs for
|
||||
* the batch.
|
||||
* - safe_strings: Internal use only. Used to store and retrieve strings
|
||||
* marked as safe between requests.
|
||||
* - progressive: A Boolean that indicates whether or not the batch needs to
|
||||
* run progressively. TRUE indicates that the batch will run in more than
|
||||
* one run. FALSE (default) indicates that the batch will finish in a single
|
||||
* run.
|
||||
* - queue: An override of the default queue (with name and class fields
|
||||
* optional). An array containing two elements:
|
||||
* - name: Unique identifier for the queue.
|
||||
* - class: The name of a class that implements
|
||||
* \Drupal\Core\Queue\QueueInterface, including the full namespace but not
|
||||
* starting with a backslash. It must have a constructor with two
|
||||
* arguments: $name and a \Drupal\Core\Database\Connection object.
|
||||
* Typically, the class will either be \Drupal\Core\Queue\Batch or
|
||||
* \Drupal\Core\Queue\BatchMemory. Defaults to Batch if progressive is
|
||||
* TRUE, or to BatchMemory if progressive is FALSE.
|
||||
*/
|
||||
function batch_set($batch_definition) {
|
||||
if ($batch_definition) {
|
||||
$batch =& batch_get();
|
||||
|
||||
// Initialize the batch if needed.
|
||||
if (empty($batch)) {
|
||||
$batch = array(
|
||||
'sets' => array(),
|
||||
'has_form_submits' => FALSE,
|
||||
);
|
||||
}
|
||||
|
||||
// Base and default properties for the batch set.
|
||||
$init = array(
|
||||
'sandbox' => array(),
|
||||
'results' => array(),
|
||||
'success' => FALSE,
|
||||
'start' => 0,
|
||||
'elapsed' => 0,
|
||||
);
|
||||
$defaults = array(
|
||||
'title' => t('Processing'),
|
||||
'init_message' => t('Initializing.'),
|
||||
'progress_message' => t('Completed @current of @total.'),
|
||||
'error_message' => t('An error has occurred.'),
|
||||
'css' => array(),
|
||||
);
|
||||
$batch_set = $init + $batch_definition + $defaults;
|
||||
|
||||
// Tweak init_message to avoid the bottom of the page flickering down after
|
||||
// init phase.
|
||||
$batch_set['init_message'] .= '<br/> ';
|
||||
|
||||
// The non-concurrent workflow of batch execution allows us to save
|
||||
// numberOfItems() queries by handling our own counter.
|
||||
$batch_set['total'] = count($batch_set['operations']);
|
||||
$batch_set['count'] = $batch_set['total'];
|
||||
|
||||
// Add the set to the batch.
|
||||
if (empty($batch['id'])) {
|
||||
// The batch is not running yet. Simply add the new set.
|
||||
$batch['sets'][] = $batch_set;
|
||||
}
|
||||
else {
|
||||
// The set is being added while the batch is running. Insert the new set
|
||||
// right after the current one to ensure execution order, and store its
|
||||
// operations in a queue.
|
||||
$index = $batch['current_set'] + 1;
|
||||
$slice1 = array_slice($batch['sets'], 0, $index);
|
||||
$slice2 = array_slice($batch['sets'], $index);
|
||||
$batch['sets'] = array_merge($slice1, array($batch_set), $slice2);
|
||||
_batch_populate_queue($batch, $index);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Processes the batch.
|
||||
*
|
||||
* This function is generally not needed in form submit handlers;
|
||||
* Form API takes care of batches that were set during form submission.
|
||||
*
|
||||
* @param \Drupal\Core\Url|string $redirect
|
||||
* (optional) Either path or Url object to redirect to when the batch has
|
||||
* finished processing.
|
||||
* @param \Drupal\Core\Url $url
|
||||
* (optional - should only be used for separate scripts like update.php)
|
||||
* URL of the batch processing page.
|
||||
* @param $redirect_callback
|
||||
* (optional) Specify a function to be called to redirect to the progressive
|
||||
* processing page.
|
||||
*
|
||||
* @return \Symfony\Component\HttpFoundation\RedirectResponse|null
|
||||
* A redirect response if the batch is progressive. No return value otherwise.
|
||||
*/
|
||||
function batch_process($redirect = NULL, Url $url = NULL, $redirect_callback = NULL) {
|
||||
$batch =& batch_get();
|
||||
|
||||
if (isset($batch)) {
|
||||
// Add process information
|
||||
$process_info = array(
|
||||
'current_set' => 0,
|
||||
'progressive' => TRUE,
|
||||
'url' => isset($url) ? $url : Url::fromRoute('system.batch_page.html'),
|
||||
'source_url' => Url::fromRouteMatch(\Drupal::routeMatch()),
|
||||
'batch_redirect' => $redirect,
|
||||
'theme' => \Drupal::theme()->getActiveTheme()->getName(),
|
||||
'redirect_callback' => $redirect_callback,
|
||||
);
|
||||
$batch += $process_info;
|
||||
|
||||
// The batch is now completely built. Allow other modules to make changes
|
||||
// to the batch so that it is easier to reuse batch processes in other
|
||||
// environments.
|
||||
\Drupal::moduleHandler()->alter('batch', $batch);
|
||||
|
||||
// Assign an arbitrary id: don't rely on a serial column in the 'batch'
|
||||
// table, since non-progressive batches skip database storage completely.
|
||||
$batch['id'] = db_next_id();
|
||||
|
||||
// Move operations to a job queue. Non-progressive batches will use a
|
||||
// memory-based queue.
|
||||
foreach ($batch['sets'] as $key => $batch_set) {
|
||||
_batch_populate_queue($batch, $key);
|
||||
}
|
||||
|
||||
// Initiate processing.
|
||||
if ($batch['progressive']) {
|
||||
// Now that we have a batch id, we can generate the redirection link in
|
||||
// the generic error message.
|
||||
/** @var \Drupal\Core\Url $batch_url */
|
||||
$batch_url = $batch['url'];
|
||||
/** @var \Drupal\Core\Url $error_url */
|
||||
$error_url = clone $batch_url;
|
||||
$query_options = $error_url->getOption('query');
|
||||
$query_options['id'] = $batch['id'];
|
||||
$query_options['op'] = 'finished';
|
||||
$error_url->setOption('query', $query_options);
|
||||
|
||||
$batch['error_message'] = t('Please continue to <a href="@error_url">the error page</a>', array('@error_url' => $error_url->toString()));
|
||||
|
||||
// Clear the way for the redirection to the batch processing page, by
|
||||
// saving and unsetting the 'destination', if there is any.
|
||||
$request = \Drupal::request();
|
||||
if ($request->query->has('destination')) {
|
||||
$batch['destination'] = $request->query->get('destination');
|
||||
$request->query->remove('destination');
|
||||
}
|
||||
|
||||
// Store safe strings.
|
||||
// @todo Ensure we are not storing an excessively large string list in:
|
||||
// https://www.drupal.org/node/2295823
|
||||
$batch['safe_strings'] = SafeMarkup::getAll();
|
||||
|
||||
// Store the batch.
|
||||
\Drupal::service('batch.storage')->create($batch);
|
||||
|
||||
// Set the batch number in the session to guarantee that it will stay alive.
|
||||
$_SESSION['batches'][$batch['id']] = TRUE;
|
||||
|
||||
// Redirect for processing.
|
||||
$query_options = $error_url->getOption('query');
|
||||
$query_options['op'] = 'start';
|
||||
$query_options['id'] = $batch['id'];
|
||||
$batch_url->setOption('query', $query_options);
|
||||
if (($function = $batch['redirect_callback']) && function_exists($function)) {
|
||||
$function($batch_url->toString(), ['query' => $query_options]);
|
||||
}
|
||||
else {
|
||||
return new RedirectResponse($batch_url->setAbsolute()->toString());
|
||||
}
|
||||
}
|
||||
else {
|
||||
// Non-progressive execution: bypass the whole progressbar workflow
|
||||
// and execute the batch in one pass.
|
||||
require_once __DIR__ . '/batch.inc';
|
||||
_batch_process();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieves the current batch.
|
||||
*/
|
||||
function &batch_get() {
|
||||
// Not drupal_static(), because Batch API operates at a lower level than most
|
||||
// use-cases for resetting static variables, and we specifically do not want a
|
||||
// global drupal_static_reset() resetting the batch information. Functions
|
||||
// that are part of the Batch API and need to reset the batch information may
|
||||
// call batch_get() and manipulate the result by reference. Functions that are
|
||||
// not part of the Batch API can also do this, but shouldn't.
|
||||
static $batch = array();
|
||||
return $batch;
|
||||
}
|
||||
|
||||
/**
|
||||
* Populates a job queue with the operations of a batch set.
|
||||
*
|
||||
* Depending on whether the batch is progressive or not, the
|
||||
* Drupal\Core\Queue\Batch or Drupal\Core\Queue\BatchMemory handler classes will
|
||||
* be used.
|
||||
*
|
||||
* @param $batch
|
||||
* The batch array.
|
||||
* @param $set_id
|
||||
* The id of the set to process.
|
||||
*
|
||||
* @return
|
||||
* The name and class of the queue are added by reference to the batch set.
|
||||
*/
|
||||
function _batch_populate_queue(&$batch, $set_id) {
|
||||
$batch_set = &$batch['sets'][$set_id];
|
||||
|
||||
if (isset($batch_set['operations'])) {
|
||||
$batch_set += array(
|
||||
'queue' => array(
|
||||
'name' => 'drupal_batch:' . $batch['id'] . ':' . $set_id,
|
||||
'class' => $batch['progressive'] ? 'Drupal\Core\Queue\Batch' : 'Drupal\Core\Queue\BatchMemory',
|
||||
),
|
||||
);
|
||||
|
||||
$queue = _batch_queue($batch_set);
|
||||
$queue->createQueue();
|
||||
foreach ($batch_set['operations'] as $operation) {
|
||||
$queue->createItem($operation);
|
||||
}
|
||||
|
||||
unset($batch_set['operations']);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns a queue object for a batch set.
|
||||
*
|
||||
* @param $batch_set
|
||||
* The batch set.
|
||||
*
|
||||
* @return
|
||||
* The queue object.
|
||||
*/
|
||||
function _batch_queue($batch_set) {
|
||||
static $queues;
|
||||
|
||||
if (!isset($queues)) {
|
||||
$queues = array();
|
||||
}
|
||||
|
||||
if (isset($batch_set['queue'])) {
|
||||
$name = $batch_set['queue']['name'];
|
||||
$class = $batch_set['queue']['class'];
|
||||
|
||||
if (!isset($queues[$class][$name])) {
|
||||
$queues[$class][$name] = new $class($name, \Drupal::database());
|
||||
}
|
||||
return $queues[$class][$name];
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @} End of "defgroup batch".
|
||||
*/
|
2307
core/includes/install.core.inc
Normal file
2307
core/includes/install.core.inc
Normal file
File diff suppressed because it is too large
Load diff
1104
core/includes/install.inc
Normal file
1104
core/includes/install.inc
Normal file
File diff suppressed because it is too large
Load diff
221
core/includes/menu.inc
Normal file
221
core/includes/menu.inc
Normal file
|
@ -0,0 +1,221 @@
|
|||
<?php
|
||||
|
||||
/**
|
||||
* @file
|
||||
* API for the Drupal menu system.
|
||||
*/
|
||||
|
||||
/**
|
||||
* @addtogroup menu
|
||||
* @{
|
||||
*/
|
||||
|
||||
use Drupal\Component\Utility\SafeMarkup;
|
||||
use Drupal\Core\Render\Element;
|
||||
|
||||
|
||||
/**
|
||||
* Prepares variables for single local task link templates.
|
||||
*
|
||||
* Default template: menu-local-task.html.twig.
|
||||
*
|
||||
* @param array $variables
|
||||
* An associative array containing:
|
||||
* - element: A render element containing:
|
||||
* - #link: A menu link array with 'title', 'url', and (optionally)
|
||||
* 'localized_options' keys.
|
||||
* - #active: A boolean indicating whether the local task is active.
|
||||
*/
|
||||
function template_preprocess_menu_local_task(&$variables) {
|
||||
$link = $variables['element']['#link'];
|
||||
$link += array(
|
||||
'localized_options' => array(),
|
||||
);
|
||||
$link_text = $link['title'];
|
||||
|
||||
if (!empty($variables['element']['#active'])) {
|
||||
$variables['is_active'] = TRUE;
|
||||
|
||||
// Add text to indicate active tab for non-visual users.
|
||||
$active = SafeMarkup::format('<span class="visually-hidden">@label</span>', array('@label' => t('(active tab)')));
|
||||
$link_text = t('@local-task-title@active', array('@local-task-title' => $link_text, '@active' => $active));
|
||||
}
|
||||
else {
|
||||
// @todo Remove this once https://www.drupal.org/node/2338081 is fixed.
|
||||
$link_text = SafeMarkup::checkPlain($link_text);
|
||||
}
|
||||
|
||||
$link['localized_options']['set_active_class'] = TRUE;
|
||||
|
||||
$variables['link'] = array(
|
||||
'#type' => 'link',
|
||||
'#title' => $link_text,
|
||||
'#url' => $link['url'],
|
||||
'#options' => $link['localized_options'],
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Prepares variables for single local action link templates.
|
||||
*
|
||||
* Default template: menu-local-action.html.twig.
|
||||
*
|
||||
* @param array $variables
|
||||
* An associative array containing:
|
||||
* - element: A render element containing:
|
||||
* - #link: A menu link array with 'title', 'url', and (optionally)
|
||||
* 'localized_options' keys.
|
||||
*/
|
||||
function template_preprocess_menu_local_action(&$variables) {
|
||||
$link = $variables['element']['#link'];
|
||||
$link += array(
|
||||
'localized_options' => array(),
|
||||
);
|
||||
$link['localized_options']['attributes']['class'][] = 'button';
|
||||
$link['localized_options']['attributes']['class'][] = 'button-action';
|
||||
$link['localized_options']['set_active_class'] = TRUE;
|
||||
|
||||
$variables['link'] = array(
|
||||
'#type' => 'link',
|
||||
'#title' => $link['title'],
|
||||
'#options' => $link['localized_options'],
|
||||
'#url' => $link['url'],
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns an array containing the names of system-defined (default) menus.
|
||||
*/
|
||||
function menu_list_system_menus() {
|
||||
return array(
|
||||
'tools' => 'Tools',
|
||||
'admin' => 'Administration',
|
||||
'account' => 'User account menu',
|
||||
'main' => 'Main navigation',
|
||||
'footer' => 'Footer menu',
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Collects the local tasks (tabs), action links, and the root path.
|
||||
*
|
||||
* @param int $level
|
||||
* The level of tasks you ask for. Primary tasks are 0, secondary are 1.
|
||||
*
|
||||
* @return array
|
||||
* An array containing
|
||||
* - tabs: Local tasks for the requested level.
|
||||
* - actions: Action links for the requested level.
|
||||
* - root_path: The router path for the current page. If the current page is
|
||||
* a default local task, then this corresponds to the parent tab.
|
||||
*
|
||||
* @see hook_menu_local_tasks()
|
||||
* @see hook_menu_local_tasks_alter()
|
||||
*/
|
||||
function menu_local_tasks($level = 0) {
|
||||
$data = &drupal_static(__FUNCTION__);
|
||||
$root_path = &drupal_static(__FUNCTION__ . ':root_path', '');
|
||||
$empty = array(
|
||||
'tabs' => array(),
|
||||
'actions' => array(),
|
||||
'root_path' => &$root_path,
|
||||
);
|
||||
|
||||
if (!isset($data)) {
|
||||
// Look for route-based tabs.
|
||||
$data['tabs'] = array();
|
||||
$data['actions'] = array();
|
||||
|
||||
$route_name = \Drupal::routeMatch()->getRouteName();
|
||||
if (!\Drupal::request()->attributes->has('exception') && !empty($route_name)) {
|
||||
$manager = \Drupal::service('plugin.manager.menu.local_task');
|
||||
$local_tasks = $manager->getTasksBuild($route_name);
|
||||
foreach ($local_tasks as $level => $items) {
|
||||
$data['tabs'][$level] = empty($data['tabs'][$level]) ? $items : array_merge($data['tabs'][$level], $items);
|
||||
}
|
||||
}
|
||||
|
||||
// Allow modules to dynamically add further tasks.
|
||||
$module_handler = \Drupal::moduleHandler();
|
||||
foreach ($module_handler->getImplementations('menu_local_tasks') as $module) {
|
||||
$function = $module . '_menu_local_tasks';
|
||||
$function($data, $route_name);
|
||||
}
|
||||
// Allow modules to alter local tasks.
|
||||
$module_handler->alter('menu_local_tasks', $data, $route_name);
|
||||
}
|
||||
|
||||
if (isset($data['tabs'][$level])) {
|
||||
return array(
|
||||
'tabs' => $data['tabs'][$level],
|
||||
'actions' => $data['actions'],
|
||||
'root_path' => $root_path,
|
||||
);
|
||||
}
|
||||
elseif (!empty($data['actions'])) {
|
||||
return array('actions' => $data['actions']) + $empty;
|
||||
}
|
||||
return $empty;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the rendered local tasks at the top level.
|
||||
*/
|
||||
function menu_primary_local_tasks() {
|
||||
$links = menu_local_tasks(0);
|
||||
// Do not display single tabs.
|
||||
return count(Element::getVisibleChildren($links['tabs'])) > 1 ? $links['tabs'] : '';
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the rendered local tasks at the second level.
|
||||
*/
|
||||
function menu_secondary_local_tasks() {
|
||||
$links = menu_local_tasks(1);
|
||||
// Do not display single tabs.
|
||||
return count(Element::getVisibleChildren($links['tabs'])) > 1 ? $links['tabs'] : '';
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the rendered local actions at the current level.
|
||||
*/
|
||||
function menu_get_local_actions() {
|
||||
$links = menu_local_tasks();
|
||||
$route_name = Drupal::routeMatch()->getRouteName();
|
||||
$manager = \Drupal::service('plugin.manager.menu.local_action');
|
||||
return $manager->getActionsForRoute($route_name) + $links['actions'];
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the router path, or the path for a default local task's parent.
|
||||
*/
|
||||
function menu_tab_root_path() {
|
||||
$links = menu_local_tasks();
|
||||
return $links['root_path'];
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns a renderable element for the primary and secondary tabs.
|
||||
*/
|
||||
function menu_local_tabs() {
|
||||
$build = array(
|
||||
'#theme' => 'menu_local_tasks',
|
||||
'#primary' => menu_primary_local_tasks(),
|
||||
'#secondary' => menu_secondary_local_tasks(),
|
||||
);
|
||||
return !empty($build['#primary']) || !empty($build['#secondary']) ? $build : array();
|
||||
}
|
||||
|
||||
/**
|
||||
* Clears all cached menu data.
|
||||
*
|
||||
* This should be called any time broad changes
|
||||
* might have been made to the router items or menu links.
|
||||
*/
|
||||
function menu_cache_clear_all() {
|
||||
\Drupal::cache('menu')->invalidateAll();
|
||||
}
|
||||
|
||||
/**
|
||||
* @} End of "addtogroup menu".
|
||||
*/
|
236
core/includes/module.inc
Normal file
236
core/includes/module.inc
Normal file
|
@ -0,0 +1,236 @@
|
|||
<?php
|
||||
|
||||
/**
|
||||
* @file
|
||||
* API for loading and interacting with Drupal modules.
|
||||
*/
|
||||
|
||||
use Drupal\Core\Cache\Cache;
|
||||
use Drupal\Core\Extension\ExtensionDiscovery;
|
||||
|
||||
/**
|
||||
* Builds a list of installed themes.
|
||||
*
|
||||
* @param $type
|
||||
* The type of list to return:
|
||||
* - theme: All installed themes.
|
||||
*
|
||||
* @return
|
||||
* An associative array of themes, keyed by name.
|
||||
* For $type 'theme', the array values are objects representing the
|
||||
* respective database row, with the 'info' property already unserialized.
|
||||
*
|
||||
* @see \Drupal\Core\Extension\ThemeHandler::listInfo()
|
||||
*/
|
||||
function system_list($type) {
|
||||
$lists = &drupal_static(__FUNCTION__);
|
||||
if ($cached = \Drupal::cache('bootstrap')->get('system_list')) {
|
||||
$lists = $cached->data;
|
||||
}
|
||||
else {
|
||||
$lists = array(
|
||||
'theme' => array(),
|
||||
'filepaths' => array(),
|
||||
);
|
||||
// ThemeHandler maintains the 'system.theme.data' state record.
|
||||
$theme_data = \Drupal::state()->get('system.theme.data', array());
|
||||
foreach ($theme_data as $name => $theme) {
|
||||
$lists['theme'][$name] = $theme;
|
||||
$lists['filepaths'][] = array(
|
||||
'type' => 'theme',
|
||||
'name' => $name,
|
||||
'filepath' => $theme->getPathname(),
|
||||
);
|
||||
}
|
||||
\Drupal::cache('bootstrap')->set('system_list', $lists);
|
||||
}
|
||||
// To avoid a separate database lookup for the filepath, prime the
|
||||
// drupal_get_filename() static cache with all enabled themes.
|
||||
foreach ($lists['filepaths'] as $item) {
|
||||
system_register($item['type'], $item['name'], $item['filepath']);
|
||||
}
|
||||
|
||||
return $lists[$type];
|
||||
}
|
||||
|
||||
/**
|
||||
* Resets all system_list() caches.
|
||||
*/
|
||||
function system_list_reset() {
|
||||
drupal_static_reset('system_list');
|
||||
drupal_static_reset('system_rebuild_module_data');
|
||||
\Drupal::cache('bootstrap')->delete('system_list');
|
||||
}
|
||||
|
||||
/**
|
||||
* Registers an extension in runtime registries for execution.
|
||||
*
|
||||
* @param string $type
|
||||
* The extension type; e.g., 'module' or 'theme'.
|
||||
* @param string $name
|
||||
* The internal name of the extension; e.g., 'node'.
|
||||
* @param string $uri
|
||||
* The relative URI of the primary extension file; e.g.,
|
||||
* 'core/modules/node/node.module'.
|
||||
*/
|
||||
function system_register($type, $name, $uri) {
|
||||
drupal_get_filename($type, $name, $uri);
|
||||
drupal_classloader_register($name, dirname($uri));
|
||||
}
|
||||
|
||||
/**
|
||||
* Loads a module's installation hooks.
|
||||
*
|
||||
* @param $module
|
||||
* The name of the module (without the .module extension).
|
||||
*
|
||||
* @return
|
||||
* The name of the module's install file, if successful; FALSE otherwise.
|
||||
*/
|
||||
function module_load_install($module) {
|
||||
// Make sure the installation API is available
|
||||
include_once __DIR__ . '/install.inc';
|
||||
|
||||
return module_load_include('install', $module);
|
||||
}
|
||||
|
||||
/**
|
||||
* Loads a module include file.
|
||||
*
|
||||
* Examples:
|
||||
* @code
|
||||
* // Load node.admin.inc from the node module.
|
||||
* module_load_include('inc', 'node', 'node.admin');
|
||||
* // Load content_types.inc from the node module.
|
||||
* module_load_include('inc', 'node', 'content_types');
|
||||
* @endcode
|
||||
*
|
||||
* Do not use this function to load an install file, use module_load_install()
|
||||
* instead. Do not use this function in a global context since it requires
|
||||
* Drupal to be fully bootstrapped, use require_once DRUPAL_ROOT . '/path/file'
|
||||
* instead.
|
||||
*
|
||||
* @param $type
|
||||
* The include file's type (file extension).
|
||||
* @param $module
|
||||
* The module to which the include file belongs.
|
||||
* @param $name
|
||||
* (optional) The base file name (without the $type extension). If omitted,
|
||||
* $module is used; i.e., resulting in "$module.$type" by default.
|
||||
*
|
||||
* @return
|
||||
* The name of the included file, if successful; FALSE otherwise.
|
||||
*
|
||||
* @todo The module_handler service has a loadInclude() method which performs
|
||||
* this same task but only for enabled modules. Figure out a way to move this
|
||||
* functionality entirely into the module_handler while keeping the ability to
|
||||
* load the files of disabled modules.
|
||||
*/
|
||||
function module_load_include($type, $module, $name = NULL) {
|
||||
if (!isset($name)) {
|
||||
$name = $module;
|
||||
}
|
||||
|
||||
if (function_exists('drupal_get_path')) {
|
||||
$file = DRUPAL_ROOT . '/' . drupal_get_path('module', $module) . "/$name.$type";
|
||||
if (is_file($file)) {
|
||||
require_once $file;
|
||||
return $file;
|
||||
}
|
||||
}
|
||||
return FALSE;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns an array of modules required by core.
|
||||
*/
|
||||
function drupal_required_modules() {
|
||||
$listing = new ExtensionDiscovery(\Drupal::root());
|
||||
$files = $listing->scan('module');
|
||||
$required = array();
|
||||
|
||||
// Unless called by the installer, an installation profile is required and
|
||||
// must always be loaded. drupal_get_profile() also returns the installation
|
||||
// profile in the installer, but only after it has been selected.
|
||||
if ($profile = drupal_get_profile()) {
|
||||
$required[] = $profile;
|
||||
}
|
||||
|
||||
foreach ($files as $name => $file) {
|
||||
$info = \Drupal::service('info_parser')->parse($file->getPathname());
|
||||
if (!empty($info) && !empty($info['required']) && $info['required']) {
|
||||
$required[] = $name;
|
||||
}
|
||||
}
|
||||
|
||||
return $required;
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets weight of a particular module.
|
||||
*
|
||||
* The weight of uninstalled modules cannot be changed.
|
||||
*
|
||||
* @param string $module
|
||||
* The name of the module (without the .module extension).
|
||||
* @param int $weight
|
||||
* An integer representing the weight of the module.
|
||||
*/
|
||||
function module_set_weight($module, $weight) {
|
||||
$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\ModuleHandler::install()
|
||||
$module_handler = \Drupal::moduleHandler();
|
||||
$current_module_filenames = $module_handler->getModuleList();
|
||||
$current_modules = array_fill_keys(array_keys($current_module_filenames), 0);
|
||||
$current_modules = module_config_sort(array_merge($current_modules, $extension_config->get('module')));
|
||||
$module_filenames = array();
|
||||
foreach ($current_modules as $name => $weight) {
|
||||
$module_filenames[$name] = $current_module_filenames[$name];
|
||||
}
|
||||
// Update the module list in the extension handler.
|
||||
$module_handler->setModuleList($module_filenames);
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Sorts the configured list of enabled modules.
|
||||
*
|
||||
* The list of enabled modules is expected to be ordered by weight and name.
|
||||
* The list is always sorted on write to avoid the overhead on read.
|
||||
*
|
||||
* @param array $data
|
||||
* An array of module configuration data.
|
||||
*
|
||||
* @return array
|
||||
* An array of module configuration data sorted by weight and name.
|
||||
*/
|
||||
function module_config_sort($data) {
|
||||
// PHP array sorting functions such as uasort() do not work with both keys and
|
||||
// values at the same time, so we achieve weight and name sorting by computing
|
||||
// strings with both information concatenated (weight first, name second) and
|
||||
// use that as a regular string sort reference list via array_multisort(),
|
||||
// compound of "[sign-as-integer][padded-integer-weight][name]"; e.g., given
|
||||
// two modules and weights (spaces added for clarity):
|
||||
// - Block with weight -5: 0 0000000000000000005 block
|
||||
// - Node with weight 0: 1 0000000000000000000 node
|
||||
$sort = array();
|
||||
foreach ($data as $name => $weight) {
|
||||
// Prefix negative weights with 0, positive weights with 1.
|
||||
// +/- signs cannot be used, since + (ASCII 43) is before - (ASCII 45).
|
||||
$prefix = (int) ($weight >= 0);
|
||||
// The maximum weight is PHP_INT_MAX, so pad all weights to 19 digits.
|
||||
$sort[] = $prefix . sprintf('%019d', abs($weight)) . $name;
|
||||
}
|
||||
array_multisort($sort, SORT_STRING, $data);
|
||||
return $data;
|
||||
}
|
335
core/includes/pager.inc
Normal file
335
core/includes/pager.inc
Normal file
|
@ -0,0 +1,335 @@
|
|||
<?php
|
||||
|
||||
/**
|
||||
* @file
|
||||
* Functions to aid in presenting database results as a set of pages.
|
||||
*/
|
||||
|
||||
use Drupal\Core\Template\Attribute;
|
||||
use Drupal\Component\Utility\UrlHelper;
|
||||
|
||||
/**
|
||||
* Returns the current page being requested for display within a pager.
|
||||
*
|
||||
* @param $element
|
||||
* An optional integer to distinguish between multiple pagers on one page.
|
||||
*
|
||||
* @return
|
||||
* The number of the current requested page, within the pager represented by
|
||||
* $element. This is determined from the URL query parameter
|
||||
* \Drupal::request()->query->get('page'), or 0 by default. Note that this
|
||||
* number may differ from the actual page being displayed. For example, if a
|
||||
* search for "example text" brings up three pages of results, but a users
|
||||
* visits search/node/example+text?page=10, this function will return 10, even
|
||||
* though the default pager implementation adjusts for this and still displays
|
||||
* the third page of search results at that URL.
|
||||
*
|
||||
* @see pager_default_initialize()
|
||||
*/
|
||||
function pager_find_page($element = 0) {
|
||||
$page = \Drupal::request()->query->get('page', '');
|
||||
$page_array = explode(',', $page);
|
||||
if (!isset($page_array[$element])) {
|
||||
$page_array[$element] = 0;
|
||||
}
|
||||
return (int) $page_array[$element];
|
||||
}
|
||||
|
||||
/**
|
||||
* Initializes a pager for _theme('pager').
|
||||
*
|
||||
* This function sets up the necessary global variables so that future calls
|
||||
* to _theme('pager') will render a pager that correctly corresponds to the
|
||||
* items being displayed.
|
||||
*
|
||||
* If the items being displayed result from a database query performed using
|
||||
* Drupal's database API, and if you have control over the construction of the
|
||||
* database query, you do not need to call this function directly; instead, you
|
||||
* can simply extend the query object with the 'PagerSelectExtender' extender
|
||||
* before executing it. For example:
|
||||
* @code
|
||||
* $query = db_select('some_table')
|
||||
* ->extend('Drupal\Core\Database\Query\PagerSelectExtender');
|
||||
* @endcode
|
||||
*
|
||||
* However, if you are using a different method for generating the items to be
|
||||
* paged through, then you should call this function in preparation.
|
||||
*
|
||||
* The following example shows how this function can be used in a page callback
|
||||
* that invokes an external datastore with an SQL-like syntax:
|
||||
* @code
|
||||
* // First find the total number of items and initialize the pager.
|
||||
* $where = "status = 1";
|
||||
* $total = mymodule_select("SELECT COUNT(*) FROM data " . $where)->result();
|
||||
* $num_per_page = \Drupal::config('mymodule.settings')->get('num_per_page');
|
||||
* $page = pager_default_initialize($total, $num_per_page);
|
||||
*
|
||||
* // Next, retrieve and display the items for the current page.
|
||||
* $offset = $num_per_page * $page;
|
||||
* $result = mymodule_select("SELECT * FROM data " . $where . " LIMIT %d, %d", $offset, $num_per_page)->fetchAll();
|
||||
* $output = drupal_render(
|
||||
* '#theme' => 'mymodule_results',
|
||||
* '#result' => $result,
|
||||
* );
|
||||
*
|
||||
* // Finally, display the pager controls, and return.
|
||||
* $pager = array('#type' => 'pager');
|
||||
* $output .= drupal_render($pager);
|
||||
* return $output;
|
||||
* @endcode
|
||||
*
|
||||
* A second example involves a page callback that invokes an external search
|
||||
* service where the total number of matching results is provided as part of
|
||||
* the returned set (so that we do not need a separate query in order to obtain
|
||||
* this information). Here, we call pager_find_page() to calculate the desired
|
||||
* offset before the search is invoked:
|
||||
* @code
|
||||
* // Perform the query, using the requested offset from pager_find_page().
|
||||
* // This comes from a URL parameter, so here we are assuming that the URL
|
||||
* // parameter corresponds to an actual page of results that will exist
|
||||
* // within the set.
|
||||
* $page = pager_find_page();
|
||||
* $num_per_page = \Drupal::config('mymodule.settings')->get('num_per_page');
|
||||
* $offset = $num_per_page * $page;
|
||||
* $result = mymodule_remote_search($keywords, $offset, $num_per_page);
|
||||
*
|
||||
* // Now that we have the total number of results, initialize the pager.
|
||||
* pager_default_initialize($result->total, $num_per_page);
|
||||
*
|
||||
* // Display the search results.
|
||||
* $search_results = array(
|
||||
* '#theme' => 'search_results',
|
||||
* '#results' => $result->data,
|
||||
* '#type' => 'remote',
|
||||
* );
|
||||
* $output = drupal_render($search_results);
|
||||
*
|
||||
* // Finally, display the pager controls, and return.
|
||||
* $pager = array('#type' => 'pager');
|
||||
* $output .= drupal_render($pager);
|
||||
* return $output;
|
||||
* @endcode
|
||||
*
|
||||
* @param $total
|
||||
* The total number of items to be paged.
|
||||
* @param $limit
|
||||
* The number of items the calling code will display per page.
|
||||
* @param $element
|
||||
* An optional integer to distinguish between multiple pagers on one page.
|
||||
*
|
||||
* @return
|
||||
* The number of the current page, within the pager represented by $element.
|
||||
* This is determined from the URL query parameter
|
||||
* \Drupal::request()->query->get('page), or 0 by default. However, if a page
|
||||
* that does not correspond to the actual range of the result set was
|
||||
* requested, this function will return the closest page actually within the
|
||||
* result set.
|
||||
*/
|
||||
function pager_default_initialize($total, $limit, $element = 0) {
|
||||
global $pager_page_array, $pager_total, $pager_total_items, $pager_limits;
|
||||
|
||||
$page = pager_find_page($element);
|
||||
|
||||
// We calculate the total of pages as ceil(items / limit).
|
||||
$pager_total_items[$element] = $total;
|
||||
$pager_total[$element] = ceil($pager_total_items[$element] / $limit);
|
||||
$pager_page_array[$element] = max(0, min($page, ((int) $pager_total[$element]) - 1));
|
||||
$pager_limits[$element] = $limit;
|
||||
return $pager_page_array[$element];
|
||||
}
|
||||
|
||||
/**
|
||||
* Compose a URL query parameter array for pager links.
|
||||
*
|
||||
* @return
|
||||
* A URL query parameter array that consists of all components of the current
|
||||
* page request except for those pertaining to paging.
|
||||
*/
|
||||
function pager_get_query_parameters() {
|
||||
$query = &drupal_static(__FUNCTION__);
|
||||
if (!isset($query)) {
|
||||
$query = UrlHelper::filterQueryParameters(\Drupal::request()->query->all(), array('page'));
|
||||
}
|
||||
return $query;
|
||||
}
|
||||
|
||||
/**
|
||||
* Prepares variables for pager templates.
|
||||
*
|
||||
* Default template: pager.html.twig.
|
||||
*
|
||||
* Menu callbacks that display paged query results should use #type => pager
|
||||
* to retrieve a pager control so that users can view other results. Format a
|
||||
* list of nearby pages with additional query results.
|
||||
*
|
||||
* @param array $variables
|
||||
* An associative array containing:
|
||||
* - pager: A render element containing:
|
||||
* - #tags: An array of labels for the controls in the pager.
|
||||
* - #element: An optional integer to distinguish between multiple pagers on
|
||||
* one page.
|
||||
* - #parameters: An associative array of query string parameters to append to
|
||||
* the pager links.
|
||||
* - #quantity: The number of pages in the list.
|
||||
*/
|
||||
function template_preprocess_pager(&$variables) {
|
||||
$element = $variables['pager']['#element'];
|
||||
$parameters = $variables['pager']['#parameters'];
|
||||
$quantity = $variables['pager']['#quantity'];
|
||||
global $pager_page_array, $pager_total;
|
||||
|
||||
// Nothing to do if there is only one page.
|
||||
if ($pager_total[$element] <= 1) {
|
||||
return;
|
||||
}
|
||||
|
||||
$tags = $variables['pager']['#tags'];
|
||||
|
||||
// Calculate various markers within this pager piece:
|
||||
// Middle is used to "center" pages around the current page.
|
||||
$pager_middle = ceil($quantity / 2);
|
||||
// current is the page we are currently paged to
|
||||
$pager_current = $pager_page_array[$element] + 1;
|
||||
// first is the first page listed by this pager piece (re quantity)
|
||||
$pager_first = $pager_current - $pager_middle + 1;
|
||||
// last is the last page listed by this pager piece (re quantity)
|
||||
$pager_last = $pager_current + $quantity - $pager_middle;
|
||||
// max is the maximum page number
|
||||
$pager_max = $pager_total[$element];
|
||||
// End of marker calculations.
|
||||
|
||||
// Prepare for generation loop.
|
||||
$i = $pager_first;
|
||||
if ($pager_last > $pager_max) {
|
||||
// Adjust "center" if at end of query.
|
||||
$i = $i + ($pager_max - $pager_last);
|
||||
$pager_last = $pager_max;
|
||||
}
|
||||
if ($i <= 0) {
|
||||
// Adjust "center" if at start of query.
|
||||
$pager_last = $pager_last + (1 - $i);
|
||||
$i = 1;
|
||||
}
|
||||
// End of generation loop preparation.
|
||||
|
||||
// Create the "first" and "previous" links if we are not on the first page.
|
||||
if ($pager_page_array[$element] > 0) {
|
||||
$items['first'] = array();
|
||||
$options = array(
|
||||
'query' => pager_query_add_page($parameters, $element, 0),
|
||||
);
|
||||
$items['first']['href'] = \Drupal::url('<current>', [], $options);
|
||||
if (isset($tags[0])) {
|
||||
$items['first']['text'] = $tags[0];
|
||||
}
|
||||
|
||||
$items['previous'] = array();
|
||||
$options = array(
|
||||
'query' => pager_query_add_page($parameters, $element, $pager_page_array[$element] - 1),
|
||||
);
|
||||
$items['previous']['href'] = \Drupal::url('<current>', [], $options);
|
||||
if (isset($tags[1])) {
|
||||
$items['previous']['text'] = $tags[1];
|
||||
}
|
||||
}
|
||||
|
||||
if ($i != $pager_max) {
|
||||
// Add an ellipsis if there are further previous pages.
|
||||
if ($i > 1) {
|
||||
$variables['ellipses']['previous'] = TRUE;
|
||||
}
|
||||
// Now generate the actual pager piece.
|
||||
for (; $i <= $pager_last && $i <= $pager_max; $i++) {
|
||||
$options = array(
|
||||
'query' => pager_query_add_page($parameters, $element, $i - 1),
|
||||
);
|
||||
$items['pages'][$i]['href'] = \Drupal::url('<current>', [], $options);
|
||||
if ($i == $pager_current) {
|
||||
$variables['current'] = $i;
|
||||
}
|
||||
}
|
||||
// Add an ellipsis if there are further next pages.
|
||||
if ($i < $pager_max) {
|
||||
$variables['ellipses']['next'] = TRUE;
|
||||
}
|
||||
}
|
||||
|
||||
// Create the "next" and "last" links if we are not on the last page.
|
||||
if ($pager_page_array[$element] < ($pager_max - 1)) {
|
||||
$items['next'] = array();
|
||||
$options = array(
|
||||
'query' => pager_query_add_page($parameters, $element, $pager_page_array[$element] + 1),
|
||||
);
|
||||
$items['next']['href'] = \Drupal::url('<current>', [], $options);
|
||||
if (isset($tags[3])) {
|
||||
$items['next']['text'] = $tags[3];
|
||||
}
|
||||
|
||||
$items['last'] = array();
|
||||
$options = array(
|
||||
'query' => pager_query_add_page($parameters, $element, $pager_max - 1),
|
||||
);
|
||||
$items['last']['href'] = \Drupal::url('<current>', [], $options);
|
||||
if (isset($tags[4])) {
|
||||
$items['last']['text'] = $tags[4];
|
||||
}
|
||||
}
|
||||
|
||||
$variables['items'] = $items;
|
||||
}
|
||||
|
||||
/**
|
||||
* Adds the 'page' parameter to the query parameter array of a pager link.
|
||||
*
|
||||
* @param array $query
|
||||
* An associative array of query parameters to add to.
|
||||
* @param integer $element
|
||||
* An integer to distinguish between multiple pagers on one page.
|
||||
* @param integer $index
|
||||
* The index of the target page in the pager array.
|
||||
*
|
||||
* @return array
|
||||
* The altered $query parameter array.
|
||||
*
|
||||
* @todo Document the pager/element/index architecture and logic. It is not
|
||||
* clear what is happening in this function as well as pager_load_array(),
|
||||
* and whether this can be simplified in any way.
|
||||
*/
|
||||
function pager_query_add_page(array $query, $element, $index) {
|
||||
global $pager_page_array;
|
||||
|
||||
// Determine the first result to display on the linked page.
|
||||
$page_new = pager_load_array($index, $element, $pager_page_array);
|
||||
|
||||
$page = \Drupal::request()->query->get('page', '');
|
||||
if ($new_page = implode(',', pager_load_array($page_new[$element], $element, explode(',', $page)))) {
|
||||
$query['page'] = $new_page;
|
||||
}
|
||||
// Merge the query parameters passed to this function with the parameters
|
||||
// from the current request. In case of collision, the parameters passed
|
||||
// into this function take precedence.
|
||||
if ($current_request_query = pager_get_query_parameters()) {
|
||||
$query = array_merge($current_request_query, $query);
|
||||
}
|
||||
return $query;
|
||||
}
|
||||
|
||||
/**
|
||||
* Helper function
|
||||
*
|
||||
* Copies $old_array to $new_array and sets $new_array[$element] = $value
|
||||
* Fills in $new_array[0 .. $element - 1] = 0
|
||||
*/
|
||||
function pager_load_array($value, $element, $old_array) {
|
||||
$new_array = $old_array;
|
||||
// Look for empty elements.
|
||||
for ($i = 0; $i < $element; $i++) {
|
||||
if (empty($new_array[$i])) {
|
||||
// Load found empty element with 0.
|
||||
$new_array[$i] = 0;
|
||||
}
|
||||
}
|
||||
// Update the changed element.
|
||||
$new_array[$element] = (int) $value;
|
||||
return $new_array;
|
||||
}
|
245
core/includes/schema.inc
Normal file
245
core/includes/schema.inc
Normal file
|
@ -0,0 +1,245 @@
|
|||
<?php
|
||||
|
||||
/**
|
||||
* @file
|
||||
* Schema API handling functions.
|
||||
*/
|
||||
|
||||
use Drupal\Core\Database\Database;
|
||||
|
||||
/**
|
||||
* @addtogroup schemaapi
|
||||
* @{
|
||||
*/
|
||||
|
||||
/**
|
||||
* Indicates that a module has not been installed yet.
|
||||
*/
|
||||
const SCHEMA_UNINSTALLED = -1;
|
||||
|
||||
/**
|
||||
* Returns an array of available schema versions for a module.
|
||||
*
|
||||
* @param string $module
|
||||
* A module name.
|
||||
*
|
||||
* @return array|bool
|
||||
* If the module has updates, an array of available updates sorted by
|
||||
* version. Otherwise, FALSE.
|
||||
*/
|
||||
function drupal_get_schema_versions($module) {
|
||||
$updates = &drupal_static(__FUNCTION__, NULL);
|
||||
if (!isset($updates[$module])) {
|
||||
$updates = array();
|
||||
foreach (\Drupal::moduleHandler()->getModuleList() as $loaded_module => $filename) {
|
||||
$updates[$loaded_module] = array();
|
||||
}
|
||||
|
||||
// Prepare regular expression to match all possible defined hook_update_N().
|
||||
$regexp = '/^(?<module>.+)_update_(?<version>\d+)$/';
|
||||
$functions = get_defined_functions();
|
||||
// Narrow this down to functions ending with an integer, since all
|
||||
// hook_update_N() functions end this way, and there are other
|
||||
// possible functions which match '_update_'. We use preg_grep() here
|
||||
// instead of foreaching through all defined functions, since the loop
|
||||
// through all PHP functions can take significant page execution time
|
||||
// and this function is called on every administrative page via
|
||||
// system_requirements().
|
||||
foreach (preg_grep('/_\d+$/', $functions['user']) as $function) {
|
||||
// If this function is a module update function, add it to the list of
|
||||
// module updates.
|
||||
if (preg_match($regexp, $function, $matches)) {
|
||||
$updates[$matches['module']][] = $matches['version'];
|
||||
}
|
||||
}
|
||||
// Ensure that updates are applied in numerical order.
|
||||
foreach ($updates as &$module_updates) {
|
||||
sort($module_updates, SORT_NUMERIC);
|
||||
}
|
||||
}
|
||||
return empty($updates[$module]) ? FALSE : $updates[$module];
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the currently installed schema version for a module.
|
||||
*
|
||||
* @param string $module
|
||||
* A module name.
|
||||
* @param bool $reset
|
||||
* Set to TRUE after installing or uninstalling an extension.
|
||||
* @param bool $array
|
||||
* Set to TRUE if you want to get information about all modules in the
|
||||
* system.
|
||||
*
|
||||
* @return string|int
|
||||
* The currently installed schema version, or SCHEMA_UNINSTALLED if the
|
||||
* module is not installed.
|
||||
*/
|
||||
function drupal_get_installed_schema_version($module, $reset = FALSE, $array = FALSE) {
|
||||
static $versions = array();
|
||||
|
||||
if ($reset) {
|
||||
$versions = array();
|
||||
}
|
||||
|
||||
if (!$versions) {
|
||||
if (!$versions = \Drupal::keyValue('system.schema')->getAll()) {
|
||||
$versions = array();
|
||||
}
|
||||
}
|
||||
|
||||
if ($array) {
|
||||
return $versions;
|
||||
}
|
||||
else {
|
||||
return isset($versions[$module]) ? $versions[$module] : SCHEMA_UNINSTALLED;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Updates the installed version information for a module.
|
||||
*
|
||||
* @param string $module
|
||||
* A module name.
|
||||
* @param string $version
|
||||
* The new schema version.
|
||||
*/
|
||||
function drupal_set_installed_schema_version($module, $version) {
|
||||
\Drupal::keyValue('system.schema')->set($module, $version);
|
||||
// Reset the static cache of module schema versions.
|
||||
drupal_get_installed_schema_version(NULL, TRUE);
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates all tables defined in a module's hook_schema().
|
||||
*
|
||||
* @param string $module
|
||||
* The module for which the tables will be created.
|
||||
*/
|
||||
function drupal_install_schema($module) {
|
||||
$schema = drupal_get_module_schema($module);
|
||||
_drupal_schema_initialize($schema, $module, FALSE);
|
||||
|
||||
foreach ($schema as $name => $table) {
|
||||
db_create_table($name, $table);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Removes all tables defined in a module's hook_schema().
|
||||
*
|
||||
* @param string $module
|
||||
* The module for which the tables will be removed.
|
||||
*
|
||||
* @return array
|
||||
* An array of arrays with the following key/value pairs:
|
||||
* - success: a boolean indicating whether the query succeeded.
|
||||
* - query: the SQL query(s) executed, passed through
|
||||
* \Drupal\Component\Utility\SafeMarkup::checkPlain().
|
||||
*/
|
||||
function drupal_uninstall_schema($module) {
|
||||
$schema = drupal_get_module_schema($module);
|
||||
_drupal_schema_initialize($schema, $module, FALSE);
|
||||
|
||||
foreach ($schema as $table) {
|
||||
if (db_table_exists($table['name'])) {
|
||||
db_drop_table($table['name']);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns a module's schema.
|
||||
*
|
||||
* This function can be used to retrieve a schema specification in
|
||||
* hook_schema(), so it allows you to derive your tables from existing
|
||||
* specifications.
|
||||
*
|
||||
* @param string $module
|
||||
* The module to which the table belongs.
|
||||
* @param string $table
|
||||
* The name of the table. If not given, the module's complete schema
|
||||
* is returned.
|
||||
*/
|
||||
function drupal_get_module_schema($module, $table = NULL) {
|
||||
// Load the .install file to get hook_schema.
|
||||
module_load_install($module);
|
||||
$schema = \Drupal::moduleHandler()->invoke($module, 'schema');
|
||||
|
||||
if (isset($table)) {
|
||||
if (isset($schema[$table])) {
|
||||
return $schema[$table];
|
||||
}
|
||||
return array();
|
||||
}
|
||||
elseif (!empty($schema)) {
|
||||
return $schema;
|
||||
}
|
||||
return array();
|
||||
}
|
||||
|
||||
/**
|
||||
* Fills in required default values for table definitions from hook_schema().
|
||||
*
|
||||
* @param array $schema
|
||||
* The schema definition array as it was returned by the module's
|
||||
* hook_schema().
|
||||
* @param string $module
|
||||
* The module for which hook_schema() was invoked.
|
||||
* @param bool $remove_descriptions
|
||||
* (optional) Whether to additionally remove 'description' keys of all tables
|
||||
* and fields to improve performance of serialize() and unserialize().
|
||||
* Defaults to TRUE.
|
||||
*/
|
||||
function _drupal_schema_initialize(&$schema, $module, $remove_descriptions = TRUE) {
|
||||
// Set the name and module key for all tables.
|
||||
foreach ($schema as $name => &$table) {
|
||||
if (empty($table['module'])) {
|
||||
$table['module'] = $module;
|
||||
}
|
||||
if (!isset($table['name'])) {
|
||||
$table['name'] = $name;
|
||||
}
|
||||
if ($remove_descriptions) {
|
||||
unset($table['description']);
|
||||
foreach ($table['fields'] as &$field) {
|
||||
unset($field['description']);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Typecasts values to proper datatypes.
|
||||
*
|
||||
* MySQL PDO silently casts, e.g. FALSE and '' to 0, when inserting the value
|
||||
* into an integer column, but PostgreSQL PDO does not. Look up the schema
|
||||
* information and use that to correctly typecast the value.
|
||||
*
|
||||
* @param array $info
|
||||
* An array describing the schema field info.
|
||||
* @param mixed $value
|
||||
* The value to be converted.
|
||||
*
|
||||
* @return mixed
|
||||
* The converted value.
|
||||
*/
|
||||
function drupal_schema_get_field_value(array $info, $value) {
|
||||
// Preserve legal NULL values.
|
||||
if (isset($value) || !empty($info['not null'])) {
|
||||
if ($info['type'] == 'int' || $info['type'] == 'serial') {
|
||||
$value = (int) $value;
|
||||
}
|
||||
elseif ($info['type'] == 'float') {
|
||||
$value = (float) $value;
|
||||
}
|
||||
elseif (!is_array($value)) {
|
||||
$value = (string) $value;
|
||||
}
|
||||
}
|
||||
return $value;
|
||||
}
|
||||
|
||||
/**
|
||||
* @} End of "addtogroup schemaapi".
|
||||
*/
|
149
core/includes/tablesort.inc
Normal file
149
core/includes/tablesort.inc
Normal file
|
@ -0,0 +1,149 @@
|
|||
<?php
|
||||
|
||||
use Drupal\Component\Utility\SafeMarkup;
|
||||
use Drupal\Core\Url;
|
||||
use Drupal\Component\Utility\UrlHelper;
|
||||
|
||||
/**
|
||||
* @file
|
||||
* Functions to aid in the creation of sortable tables.
|
||||
*
|
||||
* All tables created when rendering a '#type' => 'table' have the option of
|
||||
* having column headers that the user can click on to sort the table by that
|
||||
* column.
|
||||
*/
|
||||
|
||||
/**
|
||||
* Initializes the table sort context.
|
||||
*/
|
||||
function tablesort_init($header) {
|
||||
$ts = tablesort_get_order($header);
|
||||
$ts['sort'] = tablesort_get_sort($header);
|
||||
$ts['query'] = tablesort_get_query_parameters();
|
||||
return $ts;
|
||||
}
|
||||
|
||||
/**
|
||||
* Formats a column header.
|
||||
*
|
||||
* If the cell in question is the column header for the current sort criterion,
|
||||
* it gets special formatting. All possible sort criteria become links.
|
||||
*
|
||||
* @param string $cell_content
|
||||
* The cell content to format. Passed by reference.
|
||||
* @param array $cell_attributes
|
||||
* The cell attributes. Passed by reference.
|
||||
* @param array $header
|
||||
* An array of column headers in the format described in '#type' => 'table'.
|
||||
* @param array $ts
|
||||
* The current table sort context as returned from tablesort_init().
|
||||
*/
|
||||
function tablesort_header(&$cell_content, array &$cell_attributes, array $header, array $ts) {
|
||||
// Special formatting for the currently sorted column header.
|
||||
if (isset($cell_attributes['field'])) {
|
||||
$title = t('sort by @s', array('@s' => $cell_content));
|
||||
if ($cell_content == $ts['name']) {
|
||||
// aria-sort is a WAI-ARIA property that indicates if items in a table
|
||||
// or grid are sorted in ascending or descending order. See
|
||||
// http://www.w3.org/TR/wai-aria/states_and_properties#aria-sort
|
||||
$cell_attributes['aria-sort'] = ($ts['sort'] == 'asc') ? 'ascending' : 'descending';
|
||||
$ts['sort'] = (($ts['sort'] == 'asc') ? 'desc' : 'asc');
|
||||
$cell_attributes['class'][] = 'is-active';
|
||||
$tablesort_indicator = array(
|
||||
'#theme' => 'tablesort_indicator',
|
||||
'#style' => $ts['sort'],
|
||||
);
|
||||
$image = drupal_render($tablesort_indicator);
|
||||
}
|
||||
else {
|
||||
// If the user clicks a different header, we want to sort ascending initially.
|
||||
$ts['sort'] = 'asc';
|
||||
$image = '';
|
||||
}
|
||||
$cell_content = \Drupal::l(SafeMarkup::format('@cell_content@image', array('@cell_content' => $cell_content, '@image' => $image)), new Url('<current>', [], [
|
||||
'attributes' => array('title' => $title),
|
||||
'query' => array_merge($ts['query'], array(
|
||||
'sort' => $ts['sort'],
|
||||
'order' => $cell_content,
|
||||
)),
|
||||
]));
|
||||
|
||||
unset($cell_attributes['field'], $cell_attributes['sort']);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Composes a URL query parameter array for table sorting links.
|
||||
*
|
||||
* @return
|
||||
* A URL query parameter array that consists of all components of the current
|
||||
* page request except for those pertaining to table sorting.
|
||||
*/
|
||||
function tablesort_get_query_parameters() {
|
||||
return UrlHelper::filterQueryParameters(\Drupal::request()->query->all(), array('sort', 'order'));
|
||||
}
|
||||
|
||||
/**
|
||||
* Determines the current sort criterion.
|
||||
*
|
||||
* @param $headers
|
||||
* An array of column headers in the format described in '#type' => 'table'.
|
||||
*
|
||||
* @return
|
||||
* An associative array describing the criterion, containing the keys:
|
||||
* - "name": The localized title of the table column.
|
||||
* - "sql": The name of the database field to sort on.
|
||||
*/
|
||||
function tablesort_get_order($headers) {
|
||||
$order = \Drupal::request()->query->get('order', '');
|
||||
foreach ($headers as $header) {
|
||||
if (is_array($header)) {
|
||||
if (isset($header['data']) && $order == $header['data']) {
|
||||
$default = $header;
|
||||
break;
|
||||
}
|
||||
|
||||
if (empty($default) && isset($header['sort']) && ($header['sort'] == 'asc' || $header['sort'] == 'desc')) {
|
||||
$default = $header;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (!isset($default)) {
|
||||
$default = reset($headers);
|
||||
if (!is_array($default)) {
|
||||
$default = array('data' => $default);
|
||||
}
|
||||
}
|
||||
|
||||
$default += array('data' => NULL, 'field' => NULL);
|
||||
return array('name' => $default['data'], 'sql' => $default['field']);
|
||||
}
|
||||
|
||||
/**
|
||||
* Determines the current sort direction.
|
||||
*
|
||||
* @param $headers
|
||||
* An array of column headers in the format described in '#type' => 'table'.
|
||||
*
|
||||
* @return
|
||||
* The current sort direction ("asc" or "desc").
|
||||
*/
|
||||
function tablesort_get_sort($headers) {
|
||||
$query = \Drupal::request()->query;
|
||||
if ($query->has('sort')) {
|
||||
return (strtolower($query->get('sort')) == 'desc') ? 'desc' : 'asc';
|
||||
}
|
||||
// The user has not specified a sort. Use the default for the currently sorted
|
||||
// header if specified; otherwise use "asc".
|
||||
else {
|
||||
// Find out which header is currently being sorted.
|
||||
$ts = tablesort_get_order($headers);
|
||||
foreach ($headers as $header) {
|
||||
if (is_array($header) && isset($header['data']) && $header['data'] == $ts['name'] && isset($header['sort'])) {
|
||||
return $header['sort'];
|
||||
}
|
||||
}
|
||||
}
|
||||
return 'asc';
|
||||
}
|
1848
core/includes/theme.inc
Normal file
1848
core/includes/theme.inc
Normal file
File diff suppressed because it is too large
Load diff
163
core/includes/theme.maintenance.inc
Normal file
163
core/includes/theme.maintenance.inc
Normal file
|
@ -0,0 +1,163 @@
|
|||
<?php
|
||||
|
||||
/**
|
||||
* @file
|
||||
* Theming for maintenance pages.
|
||||
*/
|
||||
|
||||
use Drupal\Component\Utility\Unicode;
|
||||
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. Seven is always used for the initial install and update
|
||||
* operations. In other cases, Bartik is used, but this can be overridden by
|
||||
* setting a "maintenance_theme" key in the $settings variable in settings.php.
|
||||
*/
|
||||
function _drupal_maintenance_theme() {
|
||||
// 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__ . '/unicode.inc';
|
||||
require_once __DIR__ . '/file.inc';
|
||||
require_once __DIR__ . '/module.inc';
|
||||
require_once __DIR__ . '/database.inc';
|
||||
Unicode::check();
|
||||
|
||||
// Install and update pages are treated differently to prevent theming overrides.
|
||||
if (defined('MAINTENANCE_MODE') && (MAINTENANCE_MODE == 'install' || MAINTENANCE_MODE == 'update')) {
|
||||
if (drupal_installation_attempted()) {
|
||||
$custom_theme = $GLOBALS['install_state']['theme'];
|
||||
}
|
||||
else {
|
||||
$custom_theme = Settings::get('maintenance_theme', 'seven');
|
||||
}
|
||||
}
|
||||
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 $e) {
|
||||
// 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. Bartik is reasonably user friendly and fairly generic.
|
||||
$custom_theme = 'bartik';
|
||||
}
|
||||
}
|
||||
|
||||
$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 = $theme_handler->rebuildThemeData();
|
||||
$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.
|
||||
$base_theme = array();
|
||||
$ancestor = $theme;
|
||||
while ($ancestor && isset($themes[$ancestor]->base_theme)) {
|
||||
$base_theme[] = $themes[$themes[$ancestor]->base_theme];
|
||||
$ancestor = $themes[$ancestor]->base_theme;
|
||||
if ($ancestor) {
|
||||
// Ensure that the base theme is added.
|
||||
$theme_handler->addTheme($themes[$ancestor]);
|
||||
}
|
||||
}
|
||||
// @todo This is just a workaround. Find a better way how to handle themes
|
||||
// on maintenance pages, see https://www.drupal.org/node/2322619.
|
||||
\Drupal::theme()->setActiveTheme($theme_init->getActiveTheme($themes[$custom_theme], array_reverse($base_theme)));
|
||||
// Prime the theme registry.
|
||||
Drupal::service('theme.registry');
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns HTML for a results report of an operation run by authorize.php.
|
||||
*
|
||||
* @param $variables
|
||||
* An associative array containing:
|
||||
* - messages: An array of result messages.
|
||||
*
|
||||
* @ingroup themeable
|
||||
*/
|
||||
function theme_authorize_report($variables) {
|
||||
$messages = $variables['messages'];
|
||||
$output = '';
|
||||
if (!empty($messages)) {
|
||||
$output .= '<div class="authorize-results">';
|
||||
foreach ($messages as $heading => $logs) {
|
||||
$items = array();
|
||||
foreach ($logs as $number => $log_message) {
|
||||
if ($number === '#abort') {
|
||||
continue;
|
||||
}
|
||||
$authorize_message = array(
|
||||
'#theme' => 'authorize_message',
|
||||
'#message' => $log_message['message'],
|
||||
'#success' => $log_message['success'],
|
||||
);
|
||||
$items[] = drupal_render($authorize_message);
|
||||
}
|
||||
$item_list = array(
|
||||
'#theme' => 'item_list',
|
||||
'#items' => $items,
|
||||
'#title' => $heading,
|
||||
);
|
||||
$output .= drupal_render($item_list);
|
||||
}
|
||||
$output .= '</div>';
|
||||
}
|
||||
return $output;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns HTML for a single log message from the authorize.php batch operation.
|
||||
*
|
||||
* @param $variables
|
||||
* An associative array containing:
|
||||
* - message: The log message.
|
||||
* It's the caller's responsibility to ensure this string contains no
|
||||
* dangerous HTML such as SCRIPT tags.
|
||||
* - success: A boolean indicating failure or success.
|
||||
*
|
||||
* @ingroup themeable
|
||||
*/
|
||||
function theme_authorize_message($variables) {
|
||||
$message = $variables['message'];
|
||||
$success = $variables['success'];
|
||||
if ($success) {
|
||||
$item = array('data' => array('#markup' => $message), 'class' => array('authorize-results__success'));
|
||||
}
|
||||
else {
|
||||
$item = array('data' => array('#markup' => $message), 'class' => array('authorize-results__failure'));
|
||||
}
|
||||
return $item;
|
||||
}
|
112
core/includes/unicode.inc
Normal file
112
core/includes/unicode.inc
Normal file
|
@ -0,0 +1,112 @@
|
|||
<?php
|
||||
|
||||
/**
|
||||
* @file
|
||||
* Provides Unicode-related conversions and operations.
|
||||
*/
|
||||
|
||||
use Drupal\Component\Utility\Unicode;
|
||||
|
||||
/**
|
||||
* Returns Unicode library status and errors.
|
||||
*/
|
||||
function unicode_requirements() {
|
||||
$libraries = array(
|
||||
Unicode::STATUS_SINGLEBYTE => t('Standard PHP'),
|
||||
Unicode::STATUS_MULTIBYTE => t('PHP Mbstring Extension'),
|
||||
Unicode::STATUS_ERROR => t('Error'),
|
||||
);
|
||||
$severities = array(
|
||||
Unicode::STATUS_SINGLEBYTE => REQUIREMENT_WARNING,
|
||||
Unicode::STATUS_MULTIBYTE => NULL,
|
||||
Unicode::STATUS_ERROR => REQUIREMENT_ERROR,
|
||||
);
|
||||
$failed_check = Unicode::check();
|
||||
$library = Unicode::getStatus();
|
||||
|
||||
$requirements['unicode'] = array(
|
||||
'title' => t('Unicode library'),
|
||||
'value' => $libraries[$library],
|
||||
'severity' => $severities[$library],
|
||||
);
|
||||
$t_args = array('@url' => 'http://www.php.net/mbstring');
|
||||
switch ($failed_check) {
|
||||
case 'mb_strlen':
|
||||
$requirements['unicode']['description'] = t('Operations on Unicode strings are emulated on a best-effort basis. Install the <a href="@url">PHP mbstring extension</a> for improved Unicode support.', $t_args);
|
||||
break;
|
||||
|
||||
case 'mbstring.func_overload':
|
||||
$requirements['unicode']['description'] = t('Multibyte string function overloading in PHP is active and must be disabled. Check the php.ini <em>mbstring.func_overload</em> setting. Please refer to the <a href="@url">PHP mbstring documentation</a> for more information.', $t_args);
|
||||
break;
|
||||
|
||||
case 'mbstring.encoding_translation':
|
||||
$requirements['unicode']['description'] = t('Multibyte string input conversion in PHP is active and must be disabled. Check the php.ini <em>mbstring.encoding_translation</em> setting. Please refer to the <a href="@url">PHP mbstring documentation</a> for more information.', $t_args);
|
||||
break;
|
||||
|
||||
case 'mbstring.http_input':
|
||||
$requirements['unicode']['description'] = t('Multibyte string input conversion in PHP is active and must be disabled. Check the php.ini <em>mbstring.http_input</em> setting. Please refer to the <a href="@url">PHP mbstring documentation</a> for more information.', $t_args);
|
||||
break;
|
||||
|
||||
case 'mbstring.http_output':
|
||||
$requirements['unicode']['description'] = t('Multibyte string output conversion in PHP is active and must be disabled. Check the php.ini <em>mbstring.http_output</em> setting. Please refer to the <a href="@url">PHP mbstring documentation</a> for more information.', $t_args);
|
||||
break;
|
||||
}
|
||||
|
||||
return $requirements;
|
||||
}
|
||||
|
||||
/**
|
||||
* Prepares a new XML parser.
|
||||
*
|
||||
* This is a wrapper around xml_parser_create() which extracts the encoding
|
||||
* from the XML data first and sets the output encoding to UTF-8. This function
|
||||
* should be used instead of xml_parser_create(), because PHP 4's XML parser
|
||||
* doesn't check the input encoding itself. "Starting from PHP 5, the input
|
||||
* encoding is automatically detected, so that the encoding parameter specifies
|
||||
* only the output encoding."
|
||||
*
|
||||
* This is also where unsupported encodings will be converted. Callers should
|
||||
* take this into account: $data might have been changed after the call.
|
||||
*
|
||||
* @param $data
|
||||
* The XML data which will be parsed later.
|
||||
*
|
||||
* @return
|
||||
* An XML parser object or FALSE on error.
|
||||
*
|
||||
* @ingroup php_wrappers
|
||||
*/
|
||||
function drupal_xml_parser_create(&$data) {
|
||||
// Default XML encoding is UTF-8
|
||||
$encoding = 'utf-8';
|
||||
$bom = FALSE;
|
||||
|
||||
// Check for UTF-8 byte order mark (PHP5's XML parser doesn't handle it).
|
||||
if (!strncmp($data, "\xEF\xBB\xBF", 3)) {
|
||||
$bom = TRUE;
|
||||
$data = substr($data, 3);
|
||||
}
|
||||
|
||||
// Check for an encoding declaration in the XML prolog if no BOM was found.
|
||||
if (!$bom && preg_match('/^<\?xml[^>]+encoding="(.+?)"/', $data, $match)) {
|
||||
$encoding = $match[1];
|
||||
}
|
||||
|
||||
// Unsupported encodings are converted here into UTF-8.
|
||||
$php_supported = array('utf-8', 'iso-8859-1', 'us-ascii');
|
||||
if (!in_array(strtolower($encoding), $php_supported)) {
|
||||
$out = Unicode::convertToUtf8($data, $encoding);
|
||||
if ($out !== FALSE) {
|
||||
$encoding = 'utf-8';
|
||||
$data = preg_replace('/^(<\?xml[^>]+encoding)="(.+?)"/', '\\1="utf-8"', $out);
|
||||
}
|
||||
else {
|
||||
\Drupal::logger('php')->warning('Could not convert XML encoding %s to UTF-8.', array('%s' => $encoding));
|
||||
return FALSE;
|
||||
}
|
||||
}
|
||||
|
||||
$xml_parser = xml_parser_create($encoding);
|
||||
xml_parser_set_option($xml_parser, XML_OPTION_TARGET_ENCODING, 'utf-8');
|
||||
return $xml_parser;
|
||||
}
|
734
core/includes/update.inc
Normal file
734
core/includes/update.inc
Normal file
|
@ -0,0 +1,734 @@
|
|||
<?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\Component\Utility\SafeMarkup;
|
||||
use Drupal\Core\Entity\EntityStorageException;
|
||||
use Drupal\Core\Utility\Error;
|
||||
|
||||
/**
|
||||
* Disables any extensions that are incompatible with the current core version.
|
||||
*/
|
||||
function update_fix_compatibility() {
|
||||
$extension_config = \Drupal::configFactory()->getEditable('core.extension');
|
||||
$save = FALSE;
|
||||
foreach (array('module', 'theme') as $type) {
|
||||
foreach ($extension_config->get($type) as $name => $weight) {
|
||||
if (update_check_incompatibility($name, $type)) {
|
||||
$extension_config->clear("$type.$name");
|
||||
$save = TRUE;
|
||||
}
|
||||
}
|
||||
}
|
||||
if ($save) {
|
||||
$extension_config->set('module', module_config_sort($extension_config->get('module')));
|
||||
$extension_config->save();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Tests the compatibility of a module or theme.
|
||||
*/
|
||||
function update_check_incompatibility($name, $type = 'module') {
|
||||
static $themes, $modules;
|
||||
|
||||
// Store values of expensive functions for future use.
|
||||
if (empty($themes) || empty($modules)) {
|
||||
// We need to do a full rebuild here to make sure the database reflects any
|
||||
// code changes that were made in the filesystem before the update script
|
||||
// was initiated.
|
||||
$themes = \Drupal::service('theme_handler')->rebuildThemeData();
|
||||
$modules = system_rebuild_module_data();
|
||||
}
|
||||
|
||||
if ($type == 'module' && isset($modules[$name])) {
|
||||
$file = $modules[$name];
|
||||
}
|
||||
elseif ($type == 'theme' && isset($themes[$name])) {
|
||||
$file = $themes[$name];
|
||||
}
|
||||
if (!isset($file)
|
||||
|| !isset($file->info['core'])
|
||||
|| $file->info['core'] != \Drupal::CORE_COMPATIBILITY
|
||||
|| version_compare(phpversion(), $file->info['php']) < 0) {
|
||||
return TRUE;
|
||||
}
|
||||
return FALSE;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns whether the minimum schema requirement has been satisfied.
|
||||
*
|
||||
* @return array
|
||||
* A requirements info array.
|
||||
*/
|
||||
function update_system_schema_requirements() {
|
||||
$requirements = array();
|
||||
|
||||
$system_schema = drupal_get_installed_schema_version('system');
|
||||
|
||||
$requirements['minimum schema']['title'] = 'Minimum schema version';
|
||||
if ($system_schema >= \Drupal::CORE_MINIMUM_SCHEMA_VERSION) {
|
||||
$requirements['minimum schema'] += array(
|
||||
'value' => 'The installed schema version meets the minimum.',
|
||||
'description' => 'Schema version: ' . $system_schema,
|
||||
);
|
||||
}
|
||||
else {
|
||||
$requirements['minimum schema'] += array(
|
||||
'value' => 'The installed schema version does not meet the minimum.',
|
||||
'severity' => REQUIREMENT_ERROR,
|
||||
'description' => 'Your system schema version is ' . $system_schema . '. Updating directly from a schema version prior to 8000 is not supported. You must <a href="https://www.drupal.org/node/2179269">migrate your site to Drupal 8</a> first.',
|
||||
);
|
||||
}
|
||||
|
||||
return $requirements;
|
||||
}
|
||||
|
||||
/**
|
||||
* Checks update requirements and reports errors and (optionally) warnings.
|
||||
*/
|
||||
function update_check_requirements() {
|
||||
// Check requirements of all loaded modules.
|
||||
$requirements = \Drupal::moduleHandler()->invokeAll('requirements', array('update'));
|
||||
$requirements += update_system_schema_requirements();
|
||||
return $requirements;
|
||||
}
|
||||
|
||||
/**
|
||||
* Forces a module to a given schema version.
|
||||
*
|
||||
* This function is rarely necessary.
|
||||
*
|
||||
* @param string $module
|
||||
* Name of the module.
|
||||
* @param string $schema_version
|
||||
* The schema version the module should be set to.
|
||||
*/
|
||||
function update_set_schema($module, $schema_version) {
|
||||
\Drupal::keyValue('system.schema')->set($module, $schema_version);
|
||||
// system_list_reset() is in module.inc but that would only be available
|
||||
// once the variable bootstrap is done.
|
||||
require_once __DIR__ . '/module.inc';
|
||||
system_list_reset();
|
||||
}
|
||||
|
||||
/**
|
||||
* 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(t('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 $module
|
||||
* The module whose update will be run.
|
||||
* @param $number
|
||||
* The update number to run.
|
||||
* @param $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 $context
|
||||
* The batch context array.
|
||||
*
|
||||
* @see update_resolve_dependencies()
|
||||
*/
|
||||
function update_do_one($module, $number, $dependency_map, &$context) {
|
||||
$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, array($function)))) {
|
||||
return;
|
||||
}
|
||||
|
||||
$ret = array();
|
||||
if (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.
|
||||
catch (Exception $e) {
|
||||
watchdog_exception('update', $e);
|
||||
|
||||
$variables = Error::decodeException($e);
|
||||
unset($variables['backtrace']);
|
||||
// The exception message is run through
|
||||
// \Drupal\Component\Utility\SafeMarkup::checkPlain() by
|
||||
// \Drupal\Core\Utility\Error::decodeException().
|
||||
$ret['#abort'] = array('success' => FALSE, 'query' => t('%type: !message in %function (line %line of %file).', $variables));
|
||||
}
|
||||
}
|
||||
|
||||
if (isset($context['sandbox']['#finished'])) {
|
||||
$context['finished'] = $context['sandbox']['#finished'];
|
||||
unset($context['sandbox']['#finished']);
|
||||
}
|
||||
|
||||
if (!isset($context['results'][$module])) {
|
||||
$context['results'][$module] = array();
|
||||
}
|
||||
if (!isset($context['results'][$module][$number])) {
|
||||
$context['results'][$module][$number] = array();
|
||||
}
|
||||
$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_set_installed_schema_version($module, $number);
|
||||
}
|
||||
|
||||
$context['message'] = 'Updating ' . SafeMarkup::checkPlain($module) . ' module';
|
||||
}
|
||||
|
||||
/**
|
||||
* Performs entity definition updates, which can trigger schema updates.
|
||||
*
|
||||
* @param $module
|
||||
* The module whose update will be run.
|
||||
* @param $number
|
||||
* The update number to run.
|
||||
* @param $context
|
||||
* The batch context array.
|
||||
*/
|
||||
function update_entity_definitions($module, $number, &$context) {
|
||||
try {
|
||||
\Drupal::service('entity.definition_update_manager')->applyUpdates();
|
||||
}
|
||||
catch (EntityStorageException $e) {
|
||||
watchdog_exception('update', $e);
|
||||
$variables = Error::decodeException($e);
|
||||
unset($variables['backtrace']);
|
||||
// The exception message is run through
|
||||
// \Drupal\Component\Utility\SafeMarkup::checkPlain() by
|
||||
// \Drupal\Core\Utility\Error::decodeException().
|
||||
$ret['#abort'] = array('success' => FALSE, 'query' => t('%type: !message in %function (line %line of %file).', $variables));
|
||||
$context['results'][$module][$number] = $ret;
|
||||
$context['results']['#abort'][] = 'update_entity_definitions';
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns a list of all the pending database updates.
|
||||
*
|
||||
* @return
|
||||
* 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() {
|
||||
// Make sure that the system module is first in the list of updates.
|
||||
$ret = array('system' => array());
|
||||
|
||||
$modules = drupal_get_installed_schema_version(NULL, FALSE, TRUE);
|
||||
foreach ($modules as $module => $schema_version) {
|
||||
// Skip uninstalled and incompatible modules.
|
||||
if ($schema_version == SCHEMA_UNINSTALLED || update_check_incompatibility($module)) {
|
||||
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. You will need to <a href="https://www.drupal.org/node/2127611">migrate the data for this module</a> instead.';
|
||||
continue;
|
||||
}
|
||||
// Otherwise, get the list of updates defined by this module.
|
||||
$updates = drupal_get_schema_versions($module);
|
||||
if ($updates !== FALSE) {
|
||||
// \Drupal::moduleHandler()->invoke() returns NULL for non-existing hooks,
|
||||
// so if no updates are removed, it will == 0.
|
||||
$last_removed = \Drupal::moduleHandler()->invoke($module, 'update_last_removed');
|
||||
if ($schema_version < $last_removed) {
|
||||
$ret[$module]['warning'] = '<em>' . $module . '</em> module cannot be updated. Its schema version is ' . $schema_version . '. Updates up to and including ' . $last_removed . ' have been removed in this release. In order to update <em>' . $module . '</em> module, you will first <a href="https://www.drupal.org/upgrade">need to upgrade</a> to the last version in which these updates were available.';
|
||||
continue;
|
||||
}
|
||||
|
||||
$updates = array_combine($updates, $updates);
|
||||
foreach (array_keys($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 install 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);
|
||||
$description = str_replace(array("\n", '*', '/'), '', $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 $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
|
||||
* 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, array('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'] = array();
|
||||
}
|
||||
// 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 $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
|
||||
* 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) {
|
||||
// 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 = array();
|
||||
foreach ($starting_updates as $module => $version) {
|
||||
$update_functions[$module] = array();
|
||||
$updates = drupal_get_schema_versions($module);
|
||||
if ($updates !== FALSE) {
|
||||
$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 $update_functions
|
||||
* An organized array of update functions, in the format returned by
|
||||
* update_get_update_function_list().
|
||||
*
|
||||
* @return
|
||||
* 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) {
|
||||
// Initialize an array that will define a directed graph representing the
|
||||
// dependencies between update functions.
|
||||
$graph = array();
|
||||
|
||||
// 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 $module
|
||||
* The name of the module.
|
||||
* @param $number
|
||||
* The number of the update within that module.
|
||||
* @param $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
|
||||
* 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 $module
|
||||
* The name of the module.
|
||||
* @param $number
|
||||
* The number of the update within that module.
|
||||
*
|
||||
* @return
|
||||
* TRUE if the database schema indicates that the update has already been
|
||||
* performed; FALSE otherwise.
|
||||
*/
|
||||
function update_already_performed($module, $number) {
|
||||
return $number <= drupal_get_installed_schema_version($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
|
||||
* An array of return values obtained by merging the results of the
|
||||
* hook_update_dependencies() implementations in all installed modules.
|
||||
*
|
||||
* @see \Drupal::moduleHandler()->invokeAll()
|
||||
* @see hook_update_dependencies()
|
||||
*/
|
||||
function update_retrieve_dependencies() {
|
||||
$return = array();
|
||||
// Get a list of installed modules, arranged so that we invoke their hooks in
|
||||
// the same order that \Drupal::moduleHandler()->invokeAll() does.
|
||||
foreach (\Drupal::keyValue('system.schema')->getAll() as $module => $schema) {
|
||||
if ($schema == SCHEMA_UNINSTALLED) {
|
||||
// Nothing to upgrade.
|
||||
continue;
|
||||
}
|
||||
$function = $module . '_update_dependencies';
|
||||
// Ensure install file is loaded.
|
||||
module_load_install($module);
|
||||
if (function_exists($function)) {
|
||||
$result = $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($result) && is_array($result)) {
|
||||
foreach ($result as $module => $module_data) {
|
||||
foreach ($module_data as $update => $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][$update][$module_dependency]) || $update_dependency > $return[$module][$update][$module_dependency]) {
|
||||
$return[$module][$update][$module_dependency] = $update_dependency;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return $return;
|
||||
}
|
||||
|
||||
/**
|
||||
* Replace permissions during update.
|
||||
*
|
||||
* This function can replace one permission to several or even delete an old
|
||||
* one.
|
||||
*
|
||||
* @param array $replace
|
||||
* An associative array. The keys are the old permissions the values are lists
|
||||
* of new permissions. If the list is an empty array, the old permission is
|
||||
* removed.
|
||||
*/
|
||||
function update_replace_permissions($replace) {
|
||||
$prefix = 'user.role.';
|
||||
$cut = strlen($prefix);
|
||||
$role_names = \Drupal::service('config.storage')->listAll($prefix);
|
||||
foreach ($role_names as $role_name) {
|
||||
$rid = substr($role_name, $cut);
|
||||
$config = \Drupal::config("user.role.$rid");
|
||||
$permissions = $config->get('permissions') ?: array();
|
||||
foreach ($replace as $old_permission => $new_permissions) {
|
||||
if (($index = array_search($old_permission, $permissions)) !== FALSE) {
|
||||
unset($permissions[$index]);
|
||||
$permissions = array_unique(array_merge($permissions, $new_permissions));
|
||||
}
|
||||
}
|
||||
$config
|
||||
->set('permissions', $permissions)
|
||||
->save();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns a list of languages set up on the site during upgrades.
|
||||
*
|
||||
* @param $flags
|
||||
* (optional) Specifies the state of the languages that have to be returned.
|
||||
* It can be: LanguageInterface::STATE_CONFIGURABLE,
|
||||
* LanguageInterface::STATE_LOCKED, or LanguageInterface::STATE_ALL.
|
||||
*
|
||||
* @return \Drupal\Core\Language\LanguageInterface[]
|
||||
* An associative array of languages, keyed by the language code, ordered by
|
||||
* weight ascending and name ascending.
|
||||
*/
|
||||
function update_language_list($flags = LanguageInterface::STATE_CONFIGURABLE) {
|
||||
|
||||
$languages = &drupal_static(__FUNCTION__);
|
||||
|
||||
// Initialize master language list.
|
||||
if (!isset($languages)) {
|
||||
// Initialize local language list cache.
|
||||
$languages = array();
|
||||
|
||||
// Fill in master language list based on current configuration.
|
||||
$default = \Drupal::languageManager()->getDefaultLanguage();
|
||||
if (\Drupal::languageManager()->isMultilingual() || \Drupal::moduleHandler()->moduleExists('language')) {
|
||||
// Use language module configuration if available. We can not use
|
||||
// entity_load_multiple() because this breaks during updates.
|
||||
$language_entities = \Drupal::configFactory()->listAll('language.entity.');
|
||||
|
||||
// Initialize default property so callers have an easy reference and can
|
||||
// save the same object without data loss.
|
||||
foreach ($language_entities as $langcode_config_name) {
|
||||
$langcode = substr($langcode_config_name, strlen('language.entity.'));
|
||||
$info = \Drupal::config($langcode_config_name)->get();
|
||||
$languages[$langcode] = new Language(array(
|
||||
'default' => ($info['id'] == $default->getId()),
|
||||
'name' => $info['label'],
|
||||
'id' => $info['id'],
|
||||
'direction' => $info['direction'],
|
||||
'locked' => $info['locked'],
|
||||
'weight' => $info['weight'],
|
||||
));
|
||||
}
|
||||
Language::sort($languages);
|
||||
}
|
||||
else {
|
||||
// No language module, so use the default language only.
|
||||
$languages = array($default->getId() => $default);
|
||||
// Add the special languages, they will be filtered later if needed.
|
||||
$languages += \Drupal::languageManager()->getDefaultLockedLanguages($default->getWeight());
|
||||
}
|
||||
}
|
||||
|
||||
// Filter the full list of languages based on the value of the $all flag. By
|
||||
// default we remove the locked languages, but the caller may request for
|
||||
// those languages to be added as well.
|
||||
$filtered_languages = array();
|
||||
|
||||
// Add the site's default language if flagged as allowed value.
|
||||
if ($flags & LanguageInterface::STATE_SITE_DEFAULT) {
|
||||
$default = \Drupal::languageManager()->getDefaultLanguage();
|
||||
// Rename the default language.
|
||||
$default->setName(t("Site's default language (@lang_name)", array('@lang_name' => $default->getName())));
|
||||
$filtered_languages[LanguageInterface::LANGCODE_SITE_DEFAULT] = $default;
|
||||
}
|
||||
|
||||
foreach ($languages as $langcode => $language) {
|
||||
if (($language->isLocked() && !($flags & LanguageInterface::STATE_LOCKED)) || (!$language->isLocked() && !($flags & LanguageInterface::STATE_CONFIGURABLE))) {
|
||||
continue;
|
||||
}
|
||||
$filtered_languages[$langcode] = $language;
|
||||
}
|
||||
|
||||
return $filtered_languages;
|
||||
}
|
54
core/includes/utility.inc
Normal file
54
core/includes/utility.inc
Normal file
|
@ -0,0 +1,54 @@
|
|||
<?php
|
||||
|
||||
/**
|
||||
* @file
|
||||
* Miscellaneous functions.
|
||||
*/
|
||||
|
||||
use Drupal\Component\Utility\Variable;
|
||||
use Drupal\Core\PhpStorage\PhpStorageFactory;
|
||||
use Drupal\Core\Cache\Cache;
|
||||
use Drupal\Core\DrupalKernel;
|
||||
use Symfony\Component\HttpFoundation\Request;
|
||||
use Composer\Autoload\ClassLoader;
|
||||
|
||||
/**
|
||||
* Rebuilds all caches even when Drupal itself does not work.
|
||||
*
|
||||
* @param \Composer\Autoload\ClassLoader $class_loader
|
||||
* The class loader.
|
||||
* @param \Symfony\Component\HttpFoundation\Request $request
|
||||
* The current request.
|
||||
*
|
||||
* @see rebuild.php
|
||||
*/
|
||||
function drupal_rebuild(ClassLoader $class_loader, Request $request) {
|
||||
// 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();
|
||||
|
||||
// Force kernel to rebuild container.
|
||||
PhpStorageFactory::get('service_container')->deleteAll();
|
||||
PhpStorageFactory::get('twig')->deleteAll();
|
||||
|
||||
// Bootstrap up to where caches exist and clear them.
|
||||
$kernel = new DrupalKernel('prod', $class_loader);
|
||||
$kernel->setSitePath(DrupalKernel::findSitePath($request));
|
||||
$kernel->prepareLegacyRequest($request);
|
||||
|
||||
foreach (Cache::getBins() as $bin) {
|
||||
$bin->deleteAll();
|
||||
}
|
||||
|
||||
// Disable recording of cached pages.
|
||||
\Drupal::service('page_cache_kill_switch')->trigger();
|
||||
|
||||
drupal_flush_all_caches();
|
||||
|
||||
// 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 a new issue