Move all files to 2017/

This commit is contained in:
Oliver Davies 2025-09-29 22:25:17 +01:00
parent ac7370f67f
commit 2875863330
15717 changed files with 0 additions and 0 deletions

View file

@ -0,0 +1,557 @@
<?php
/**
* @file
* Batch processing API for processes to run in multiple HTTP requests.
*
* Note that batches are usually invoked by form submissions, which is
* why the core interaction functions of the batch processing API live in
* form.inc.
*
* @see form.inc
* @see batch_set()
* @see batch_process()
* @see batch_get()
*/
use Drupal\Component\Utility\Timer;
use Drupal\Component\Utility\UrlHelper;
use Drupal\Core\Batch\Percentage;
use Drupal\Core\Form\FormState;
use Drupal\Core\Url;
use Symfony\Component\HttpFoundation\JsonResponse;
use Symfony\Component\HttpFoundation\Request;
use Symfony\Component\HttpFoundation\RedirectResponse;
/**
* Renders the batch processing page based on the current state of the batch.
*
* @param \Symfony\Component\HttpFoundation\Request $request
* The current request object.
*
* @see _batch_shutdown()
*/
function _batch_page(Request $request) {
$batch = &batch_get();
if (!($request_id = $request->query->get('id'))) {
return FALSE;
}
// Retrieve the current state of the batch.
if (!$batch) {
$batch = \Drupal::service('batch.storage')->load($request_id);
if (!$batch) {
\Drupal::messenger()->addError(t('No active batch.'));
return new RedirectResponse(\Drupal::url('<front>', [], ['absolute' => TRUE]));
}
}
// We need to store the updated batch information in the batch storage after
// processing the batch. In order for the error page to work correctly this
// needs to be done even in case of a PHP fatal error in which case the end of
// this function is never reached. Therefore we register a shutdown function
// to handle this case. Because with FastCGI and fastcgi_finish_request()
// shutdown functions are called after the HTTP connection is closed, updating
// the batch information in a shutdown function would lead to race conditions
// between consecutive requests if the batch processing continues. In case of
// a fatal error the processing stops anyway, so it works even with FastCGI.
// However, we must ensure to only update in the shutdown phase in this
// particular case we track whether the batch information still needs to be
// updated.
// @see _batch_shutdown()
// @see \Symfony\Component\HttpFoundation\Response::send()
drupal_register_shutdown_function('_batch_shutdown');
_batch_needs_update(TRUE);
$build = [];
// Add batch-specific libraries.
foreach ($batch['sets'] as $batch_set) {
if (isset($batch_set['library'])) {
foreach ($batch_set['library'] as $library) {
$build['#attached']['library'][] = $library;
}
}
}
$op = $request->query->get('op', '');
switch ($op) {
case 'start':
case 'do_nojs':
// Display the full progress page on startup and on each additional
// non-JavaScript iteration.
$current_set = _batch_current_set();
$build['#title'] = $current_set['title'];
$build['content'] = _batch_progress_page();
$response = $build;
break;
case 'do':
// JavaScript-based progress page callback.
$response = _batch_do();
break;
case 'finished':
// _batch_finished() returns a RedirectResponse.
$response = _batch_finished();
break;
}
if ($batch) {
\Drupal::service('batch.storage')->update($batch);
}
_batch_needs_update(FALSE);
return $response;
}
/**
* Checks whether the batch information needs to be updated in the storage.
*
* @param bool $new_value
* (optional) A new value to set.
*
* @return bool
* TRUE if the batch information needs to be updated; FALSE otherwise.
*/
function _batch_needs_update($new_value = NULL) {
$needs_update = &drupal_static(__FUNCTION__, FALSE);
if (isset($new_value)) {
$needs_update = $new_value;
}
return $needs_update;
}
/**
* Does one execution pass with JavaScript and returns progress to the browser.
*
* @see _batch_progress_page_js()
* @see _batch_process()
*/
function _batch_do() {
// Perform actual processing.
list($percentage, $message, $label) = _batch_process();
return new JsonResponse(['status' => TRUE, 'percentage' => $percentage, 'message' => $message, 'label' => $label]);
}
/**
* Outputs a batch processing page.
*
* @see _batch_process()
*/
function _batch_progress_page() {
$batch = &batch_get();
$current_set = _batch_current_set();
$new_op = 'do_nojs';
if (!isset($batch['running'])) {
// This is the first page so we return some output immediately.
$percentage = 0;
$message = $current_set['init_message'];
$label = '';
$batch['running'] = TRUE;
}
else {
// This is one of the later requests; do some processing first.
// Error handling: if PHP dies due to a fatal error (e.g. a nonexistent
// function), it will output whatever is in the output buffer, followed by
// the error message.
ob_start();
$fallback = $current_set['error_message'] . '<br />' . $batch['error_message'];
// We strip the end of the page using a marker in the template, so any
// additional HTML output by PHP shows up inside the page rather than below
// it. While this causes invalid HTML, the same would be true if we didn't,
// as content is not allowed to appear after </html> anyway.
$bare_html_page_renderer = \Drupal::service('bare_html_page_renderer');
$response = $bare_html_page_renderer->renderBarePage(['#markup' => $fallback], $current_set['title'], 'maintenance_page', [
'#show_messages' => FALSE,
]);
// Just use the content of the response.
$fallback = $response->getContent();
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(TRUE)->getGeneratedUrl();
$build = [
'#theme' => 'progress_bar',
'#percent' => $percentage,
'#message' => ['#markup' => $message],
'#label' => $label,
'#attached' => [
'html_head' => [
[
[
// Redirect through a 'Refresh' meta tag if JavaScript is disabled.
'#tag' => 'meta',
'#noscript' => TRUE,
'#attributes' => [
'http-equiv' => 'Refresh',
'content' => '0; URL=' . $url,
],
],
'batch_progress_meta_refresh',
],
],
// Adds JavaScript code and settings for clients where JavaScript is enabled.
'drupalSettings' => [
'batch' => [
'errorMessage' => $current_set['error_message'] . '<br />' . $batch['error_message'],
'initMessage' => $current_set['init_message'],
'uri' => $url,
],
],
'library' => [
'core/drupal.batch',
],
],
];
return $build;
}
/**
* Processes sets in a batch.
*
* If the batch was marked for progressive execution (default), this executes as
* many operations in batch sets until an execution time of 1 second has been
* exceeded. It will continue with the next operation of the same batch set in
* the next request.
*
* @return array
* An array containing a completion value (in percent) and a status message.
*/
function _batch_process() {
$batch = &batch_get();
$current_set = &_batch_current_set();
// Indicate that this batch set needs to be initialized.
$set_changed = TRUE;
// 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 = [
'sandbox' => &$current_set['sandbox'],
'results' => &$current_set['results'],
'finished' => &$finished,
'message' => &$task_message,
];
call_user_func_array($callback, array_merge($args, [&$batch_context]));
if ($finished >= 1) {
// Make sure this step is not counted twice when computing $current.
$finished = 0;
// Remove the processed operation and clear the sandbox.
$queue->deleteItem($item);
$current_set['count']--;
$current_set['sandbox'] = [];
}
}
// When all operations in the current batch set are completed, browse
// through the remaining sets, marking them 'successfully processed'
// along the way, until we find a set that contains operations.
// _batch_next_set() executes form submit handlers stored in 'control'
// sets (see \Drupal::service('form_submitter')), which can in turn add new
// sets to the batch.
$set_changed = FALSE;
$old_set = $current_set;
while (empty($current_set['count']) && ($current_set['success'] = TRUE) && _batch_next_set()) {
$current_set = &_batch_current_set();
$current_set['start'] = microtime(TRUE);
$set_changed = TRUE;
}
// At this point, either $current_set contains operations that need to be
// processed or all sets have been completed.
$queue = _batch_queue($current_set);
// If we are in progressive mode, break processing after 1 second.
if ($batch['progressive'] && Timer::read('batch_processing') > 1000) {
// Record elapsed wall clock time.
$current_set['elapsed'] = round((microtime(TRUE) - $current_set['start']) * 1000, 2);
break;
}
}
if ($batch['progressive']) {
// Gather progress information.
// Reporting 100% progress will cause the whole batch to be considered
// processed. If processing was paused right after moving to a new set,
// we have to use the info from the new (unprocessed) set.
if ($set_changed && isset($current_set['queue'])) {
// Processing will continue with a fresh batch set.
$remaining = $current_set['count'];
$total = $current_set['total'];
$progress_message = $current_set['init_message'];
$task_message = '';
}
else {
// Processing will continue with the current batch set.
$remaining = $old_set['count'];
$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 = [
'@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 [$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 int $total
* The total number of operations.
* @param int|float $current
* The number of the current operation. This may be a floating point number
* rather than an integer in the case of a multi-step operation that is not
* yet complete; in that case, the fractional part of $current represents the
* fraction of the operation that has been completed.
*
* @return string
* The properly formatted percentage, as a string. We output percentages
* using the correct number of decimal places so that we never print "100%"
* until we are finished, but we also never print more decimal places than
* are meaningful.
*
* @see _batch_process()
*/
function _batch_api_percentage($total, $current) {
return Percentage::format($total, $current);
}
/**
* Returns the batch set being currently processed.
*/
function &_batch_current_set() {
$batch = &batch_get();
return $batch['sets'][$batch['current_set']];
}
/**
* Retrieves the next set in a batch.
*
* If there is a subsequent set in this batch, assign it as the new set to
* process and execute its form submit handler (if defined), which may add
* further sets to this batch.
*
* @return true|null
* TRUE if a subsequent set was found in the batch; no value will be returned
* if no subsequent set was found.
*/
function _batch_next_set() {
$batch = &batch_get();
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, [&$complete_form, &$batch['form_state']]);
}
return TRUE;
}
}
/**
* Ends the batch processing.
*
* Call the 'finished' callback of each batch set to allow custom handling of
* the results and resolve page redirection.
*/
function _batch_finished() {
$batch = &batch_get();
$batch_finished_redirect = NULL;
// Execute the 'finished' callbacks for each batch set, if defined.
foreach ($batch['sets'] as $batch_set) {
if (isset($batch_set['finished'])) {
// Check if the set requires an additional file for function definitions.
if (isset($batch_set['file']) && is_file($batch_set['file'])) {
include_once \Drupal::root() . '/' . $batch_set['file'];
}
if (is_callable($batch_set['finished'])) {
$queue = _batch_queue($batch_set);
$operations = $queue->getAllItems();
$batch_set_result = call_user_func_array($batch_set['finished'], [$batch_set['success'], $batch_set['results'], $operations, \Drupal::service('date.formatter')->formatInterval($batch_set['elapsed'] / 1000)]);
// If a batch 'finished' callback requested a redirect after the batch
// is complete, save that for later use. If more than one batch set
// returned a redirect, the last one is used.
if ($batch_set_result instanceof RedirectResponse) {
$batch_finished_redirect = $batch_set_result;
}
}
}
}
// Clean up the batch table and unset the static $batch variable.
if ($batch['progressive']) {
\Drupal::service('batch.storage')->delete($batch['id']);
foreach ($batch['sets'] as $batch_set) {
if ($queue = _batch_queue($batch_set)) {
$queue->deleteQueue();
}
}
// Clean-up the session. Not needed for CLI updates.
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 a batch 'finished' callback
// returned a redirect response object, use that. Otherwise, fall back on
// the form redirection.
if (isset($batch_finished_redirect)) {
return $batch_finished_redirect;
}
elseif (!isset($_batch['form_state'])) {
$_batch['form_state'] = new FormState();
}
if ($_batch['form_state']->getRedirect() === NULL) {
$redirect = $_batch['batch_redirect'] ?: $_batch['source_url'];
// Any path with a scheme does not correspond to a route.
if (!$redirect instanceof Url) {
$options = UrlHelper::parse($redirect);
if (parse_url($options['path'], PHP_URL_SCHEME)) {
$redirect = Url::fromUri($options['path'], $options);
}
else {
$redirect = \Drupal::pathValidator()->getUrlIfValid($options['path']);
if (!$redirect) {
// Stay on the same page if the redirect was invalid.
$redirect = Url::fromRoute('<current>');
}
$redirect->setOptions($options);
}
}
$_batch['form_state']->setRedirectUrl($redirect);
}
// Use \Drupal\Core\Form\FormSubmitterInterface::redirectForm() to handle
// the redirection logic.
$redirect = \Drupal::service('form_submitter')->redirectForm($_batch['form_state']);
if (is_object($redirect)) {
return $redirect;
}
// If no redirection happened, redirect to the originating page. In case the
// form needs to be rebuilt, save the final $form_state for
// \Drupal\Core\Form\FormBuilderInterface::buildForm().
if ($_batch['form_state']->isRebuilding()) {
$_SESSION['batch_form_state'] = $_batch['form_state'];
}
$callback = $_batch['redirect_callback'];
$_batch['source_url']->mergeOptions(['query' => ['op' => 'finish', 'id' => $_batch['id']]]);
if (is_callable($callback)) {
$callback($_batch['source_url'], $_batch['source_url']->getOption('query'));
}
elseif ($callback === NULL) {
// Default to RedirectResponse objects when nothing specified.
return new RedirectResponse($_batch['source_url']->setAbsolute()->toString());
}
}
}
/**
* Shutdown function: Stores the current batch data for the next request.
*
* @see _batch_page()
* @see drupal_register_shutdown_function()
*/
function _batch_shutdown() {
if (($batch = batch_get()) && _batch_needs_update()) {
\Drupal::service('batch.storage')->update($batch);
}
}

File diff suppressed because it is too large Load diff

File diff suppressed because it is too large Load diff

File diff suppressed because it is too large Load diff

View file

@ -0,0 +1,575 @@
<?php
/**
* @file
* Entity API for handling entities like nodes or users.
*/
use Drupal\Core\Entity\EntityInterface;
use Drupal\Core\Entity\Entity\EntityFormDisplay;
use Drupal\Core\Entity\Entity\EntityViewDisplay;
/**
* 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.
*
* @deprecated in Drupal 8.x-dev and will be removed before Drupal 9.0.0. Use
* \Drupal\Core\Entity\EntityTypeBundleInfoInterface::getBundleInfo() for a
* single bundle, or
* \Drupal\Core\Entity\EntityTypeBundleInfoInterface::getAllBundleInfo() for
* all bundles.
*
* @see \Drupal\Core\Entity\EntityTypeBundleInfoInterface::getBundleInfo()
* @see \Drupal\Core\Entity\EntityTypeBundleInfoInterface::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.
*
* @deprecated in Drupal 8.0.x, will be removed before Drupal 9.0.0. Use
* The method overriding Entity::load() for the entity type, e.g.
* \Drupal\node\Entity\Node::load() if the entity type is known. If the
* entity type is variable, use the entity manager service to load the entity
* from the entity storage:
* @code
* \Drupal::entityTypeManager()->getStorage($entity_type)->load($id);
* @endcode
*
* @see \Drupal\Core\Entity\EntityInterface::load()
* @see \Drupal\Core\Entity\EntityTypeManagerInterface::getStorage()
* @see \Drupal\Core\Entity\EntityStorageInterface::load()
* @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([$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.
*
* @deprecated as of Drupal 8.0.x, will be removed before Drupal 9.0.0. Use
* the entity storage's loadRevision() method to load a specific entity
* revision:
* @code
* \Drupal::entityTypeManager()
* ->getStorage($entity_type)
* ->loadRevision($revision_id);
* @endcode
*
* @see \Drupal\Core\Entity\EntityTypeManagerInterface::getStorage()
* @see \Drupal\Core\Entity\EntityStorageInterface::loadRevision()
* @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.
*
* @deprecated as of Drupal 8.0.x, will be removed before Drupal 9.0.0. Use
* the entity storage's deleteRevision() method to delete a specific entity
* revision:
* @code
* \Drupal::entityTypeManager()
* ->getStorage($entity_type)
* ->deleteRevision($revision_id);
* @endcode
*
* @see \Drupal\Core\Entity\EntityTypeManagerInterface::getStorage()
* @see \Drupal\Core\Entity\EntityStorageInterface::deleteRevision()
*/
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
* "handlers['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.
*
* @deprecated in Drupal 8.0.x, will be removed before Drupal 9.0.0. Use
* The method overriding Entity::loadMultiple() for the entity type, e.g.
* \Drupal\node\Entity\Node::loadMultiple() if the entity type is known. If
* the entity type is variable, use the entity manager service to load the
* entity from the entity storage:
* @code
* \Drupal::entityTypeManager()->getStorage($entity_type)->loadMultiple($id);
* @endcode
*
* @see \Drupal\Core\Entity\EntityInterface::loadMultiple()
* @see \Drupal\Core\Entity\EntityTypeManagerInterface::getStorage()
* @see \Drupal\Core\Entity\EntityStorageInterface::loadMultiple()
* @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.
*
* @deprecated as of Drupal 8.0.x, will be removed before Drupal 9.0.0. Use
* the entity storage's loadByProperties() method to load an entity by their
* property values:
* @code
* \Drupal::entityTypeManager()
* ->getStorage($entity_type)
* ->loadByProperties($values);
* @endcode
*
* @see \Drupal\Core\Entity\EntityTypeManagerInterface::getStorage()
* @see \Drupal\Core\Entity\EntityStorageInterface::loadByProperties()
*/
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 \Drupal\Core\Entity\EntityInterface|null
* The unchanged entity, or FALSE if the entity cannot be loaded.
*
* @deprecated as of Drupal 8.0.x, will be removed before Drupal 9.0.0. Use
* the entity storage's loadUnchanged() method to load an unchanged entity:
* @code
* \Drupal::entityTypeManager()->getStorage($entity_type)->loadUnchanged($id);
* @endcode
*
* @see \Drupal\Core\Entity\EntityTypeManagerInterface::getStorage()
* @see \Drupal\Core\Entity\EntityStorageInterface::loadUnchanged()
*/
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.
*
* @deprecated as of Drupal 8.0.x, will be removed before Drupal 9.0.0. Use
* the entity storage's delete() method to delete multiple entities:
* @code
* $storage_handler = \Drupal::entityTypeManager()->getStorage($entity_type);
* $entities = $storage_handler->loadMultiple($ids);
* $storage_handler->delete($entities);
* @endcode
*
* @see \Drupal\Core\Entity\EntityTypeManagerInterface::getStorage()
* @see \Drupal\Core\Entity\EntityStorageInterface::loadMultiple()
* @see \Drupal\Core\Entity\EntityStorageInterface::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.0.x, will be removed before Drupal 9.0.0. Use
* The method overriding Entity::create() for the entity type, e.g.
* \Drupal\node\Entity\Node::create() if the entity type is known. If the
* entity type is variable, use the entity storage's create() method to
* construct a new entity:
* @code
* \Drupal::entityTypeManager()->getStorage($entity_type)->create($values);
* @endcode
*
* @see \Drupal\Core\Entity\EntityTypeManagerInterface::getStorage()
* @see \Drupal\Core\Entity\EntityStorageInterface::create()
*/
function entity_create($entity_type, array $values = []) {
return \Drupal::entityManager()
->getStorage($entity_type)
->create($values);
}
/**
* Returns the label of an entity.
*
* @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 string|null
* The label of the entity, or NULL if there is no label defined.
*
* @deprecated as of Drupal 8.0.x, will be removed before Drupal 9.0.0. Use
* the entity's label() method to get the label of the entity:
* @code
* $entity->label($langcode);
* @endcode
*
* @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.
*
* @deprecated as of Drupal 8.0.x, will be removed before Drupal 9.0.0.
* Use the entity view builder's view() method for creating a render array:
* @code
* $view_builder = \Drupal::entityTypeManager()
* ->getViewBuilder($entity->getEntityTypeId());
* return $view_builder->view($entity, $view_mode, $langcode);
* @endcode
*
* @see \Drupal\Core\Entity\EntityTypeManagerInterface::getViewBuilder()
* @see \Drupal\Core\Entity\EntityViewBuilderInterface::view()
*/
function entity_view(EntityInterface $entity, $view_mode, $langcode = NULL, $reset = FALSE) {
$render_controller = \Drupal::entityManager()->getViewBuilder($entity->getEntityTypeId());
if ($reset) {
$render_controller->resetCache([$entity]);
}
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.
*
* @deprecated as of Drupal 8.0.x, will be removed before Drupal 9.0.0.
* Use the entity view builder's viewMultiple() method for creating a render
* array for the provided entities:
* @code
* $view_builder = \Drupal::entityTypeManager()
* ->getViewBuilder($entity->getEntityTypeId());
* return $view_builder->viewMultiple($entities, $view_mode, $langcode);
* @endcode
*
* @see \Drupal\Core\Entity\EntityTypeManagerInterface::getViewBuilder()
* @see \Drupal\Core\Entity\EntityViewBuilderInterface::viewMultiple()
*/
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($entities);
}
return $render_controller->viewMultiple($entities, $view_mode, $langcode);
}
/**
* Returns the entity view display associated with 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 with the view mode.
*
* @deprecated as of Drupal 8.0.x, will be removed before Drupal 9.0.0.
* If the display is available in configuration use:
* @code
* \Drupal::entityTypeManager()
* ->getStorage('entity_view_display')
* ->load($entity_type . '.' . $bundle . '.' . $view_mode);
* @endcode
* When the display is not available in configuration, you can create a new
* EntityViewDisplay object using:
* @code
* $values = array(
* 'targetEntityType' => $entity_type,
* 'bundle' => $bundle,
* 'mode' => $view_mode,
* 'status' => TRUE,
* );
* \Drupal::entityTypeManager()
* ->getStorage('entity_view_display')
* ->create($values);
* @endcode
*
* @see \Drupal\Core\Entity\EntityStorageInterface::create()
* @see \Drupal\Core\Entity\EntityStorageInterface::load()
*/
function entity_get_display($entity_type, $bundle, $view_mode) {
// Try loading the display from configuration.
$display = EntityViewDisplay::load($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 = EntityViewDisplay::create([
'targetEntityType' => $entity_type,
'bundle' => $bundle,
'mode' => $view_mode,
'status' => TRUE,
]);
}
return $display;
}
/**
* Returns the entity form display associated with 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(
* 'region' => '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 with the given form mode.
*
* @deprecated as of Drupal 8.0.x, will be removed before Drupal 9.0.0.
* If the entity form display is available in configuration use:
* @code
* \Drupal::entityTypeManager()
* ->getStorage('entity_form_display')
* ->load($entity_type . '.' . $bundle . '.' . $form_mode);
* @endcode
* When the entity form display is not available in configuration, you can
* create a new EntityFormDisplay object using:
* @code
* $values = array(
* 'targetEntityType' => $entity_type,
* 'bundle' => $bundle,
* 'mode' => $form_mode,
* 'status' => TRUE,
* );
* \Drupal::entityTypeManager()
* ->getStorage('entity_form_display')
* ->create($values);
* @endcode
*
* @see \Drupal\Core\Entity\EntityStorageInterface::create()
* @see \Drupal\Core\Entity\EntityStorageInterface::load()
*/
function entity_get_form_display($entity_type, $bundle, $form_mode) {
// Try loading the entity from configuration.
$entity_form_display = EntityFormDisplay::load($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 = EntityFormDisplay::create([
'targetEntityType' => $entity_type,
'bundle' => $bundle,
'mode' => $form_mode,
'status' => TRUE,
]);
}
return $entity_form_display;
}

View file

@ -0,0 +1,358 @@
<?php
/**
* @file
* Functions for error handling.
*/
use Drupal\Component\Render\FormattableMarkup;
use Drupal\Component\Utility\Xss;
use Drupal\Core\Logger\RfcLogLevel;
use Drupal\Core\Render\Markup;
use Drupal\Core\Utility\Error;
use Symfony\Component\HttpFoundation\Response;
/**
* Maps PHP error constants to watchdog severity levels.
*
* The error constants are documented at
* http://php.net/manual/errorfunc.constants.php
*
* @ingroup logging_severity_levels
*/
function drupal_error_levels() {
$types = [
E_ERROR => ['Error', RfcLogLevel::ERROR],
E_WARNING => ['Warning', RfcLogLevel::WARNING],
E_PARSE => ['Parse error', RfcLogLevel::ERROR],
E_NOTICE => ['Notice', RfcLogLevel::NOTICE],
E_CORE_ERROR => ['Core error', RfcLogLevel::ERROR],
E_CORE_WARNING => ['Core warning', RfcLogLevel::WARNING],
E_COMPILE_ERROR => ['Compile error', RfcLogLevel::ERROR],
E_COMPILE_WARNING => ['Compile warning', RfcLogLevel::WARNING],
E_USER_ERROR => ['User error', RfcLogLevel::ERROR],
E_USER_WARNING => ['User warning', RfcLogLevel::WARNING],
E_USER_NOTICE => ['User notice', RfcLogLevel::NOTICE],
E_STRICT => ['Strict warning', RfcLogLevel::DEBUG],
E_RECOVERABLE_ERROR => ['Recoverable fatal error', RfcLogLevel::ERROR],
E_DEPRECATED => ['Deprecated function', RfcLogLevel::DEBUG],
E_USER_DEPRECATED => ['User deprecated function', RfcLogLevel::DEBUG],
];
return $types;
}
/**
* Provides custom PHP error handling.
*
* @param $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.
$recoverable = $error_level == E_RECOVERABLE_ERROR;
// As __toString() methods must not throw exceptions (recoverable errors)
// in PHP, we allow them to trigger a fatal error by emitting a user error
// using trigger_error().
$to_string = $error_level == E_USER_ERROR && substr($caller['function'], -strlen('__toString()')) == '__toString()';
_drupal_log_error([
'%type' => isset($types[$error_level]) ? $severity_msg : 'Unknown error',
// The standard PHP error handler considers that the error messages
// are HTML. We mimick this behavior here.
'@message' => Markup::create(Xss::filterAdmin($message)),
'%function' => $caller['function'],
'%file' => $caller['file'],
'%line' => $caller['line'],
'severity_level' => $severity_level,
'backtrace' => $backtrace,
'@backtrace_string' => (new \Exception())->getTraceAsString(),
], $recoverable || $to_string);
}
// If the site is a test site then fail for user deprecations so they can be
// caught by the deprecation error handler.
elseif (DRUPAL_TEST_IN_CHILD_SITE && $error_level === E_USER_DEPRECATED) {
$backtrace = debug_backtrace();
$caller = Error::getLastCaller($backtrace);
_drupal_error_header(
Markup::create(Xss::filterAdmin($message)),
'User deprecated function',
$caller['function'],
$caller['file'],
$caller['line']
);
}
}
/**
* Determines whether an error should be displayed.
*
* When in maintenance mode or when error_level is ERROR_REPORTING_DISPLAY_ALL,
* all errors should be displayed. For ERROR_REPORTING_DISPLAY_SOME, $error
* will be examined to determine if it should be displayed.
*
* @param $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, @backtrace_string, severity_level, and backtrace. All the parameters
* are plain-text, with the exception of @message, which needs to be an HTML
* string, and backtrace, which is a standard PHP backtrace.
* @param bool $fatal
* TRUE for:
* - An exception is thrown and not caught by something else.
* - A recoverable fatal error, which is a fatal error.
* Non-recoverable fatal errors cannot be logged by Drupal.
*/
function _drupal_log_error($error, $fatal = FALSE) {
$is_installer = drupal_installation_attempted();
// 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)) {
_drupal_error_header($error['@message'], $error['%type'], $error['%function'], $error['%file'], $error['%line']);
}
$response = new Response();
// Only call the logger if there is a logger factory available. This can occur
// if there is an error while rebuilding the container or during the
// installer.
if (\Drupal::hasService('logger.factory')) {
try {
// Provide the PHP backtrace to logger implementations.
\Drupal::logger('php')->log($error['severity_level'], '%type: @message in %function (line %line of %file) @backtrace_string.', $error + ['backtrace' => $backtrace]);
}
catch (\Exception $e) {
// We can't log, for example because the database connection is not
// available. At least try to log to PHP error log.
error_log(strtr('Failed to log error: %type: @message in %function (line %line of %file). @backtrace_string', $error));
}
}
// Log fatal errors, so developers can find and debug them.
if ($fatal) {
error_log(sprintf('%s: %s in %s on line %d %s', $error['%type'], $error['@message'], $error['%file'], $error['%line'], $error['@backtrace_string']));
}
if (PHP_SAPI === 'cli') {
if ($fatal) {
// When called from CLI, simply output a plain text message.
// Should not translate the string to avoid errors producing more errors.
$response->setContent(html_entity_decode(strip_tags(new FormattableMarkup('%type: @message in %function (line %line of %file).', $error))) . "\n");
$response->send();
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.
$response->setContent(new FormattableMarkup('%type: @message in %function (line %line of %file).', $error));
$response->send();
}
exit;
}
}
else {
// Display the message if the current error reporting level allows this type
// of message to be displayed, and unconditionally in update.php.
$message = '';
$class = NULL;
if (error_displayable($error)) {
$class = 'error';
// If error type is 'User notice' then treat it as debug information
// instead of an error message.
// @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);
}
}
// Check if verbose error reporting is on.
$error_level = _drupal_get_error_level();
if ($error_level != ERROR_REPORTING_DISPLAY_VERBOSE) {
// Without verbose logging, use a simple message.
// We use \Drupal\Component\Render\FormattableMarkup directly here,
// rather than use t() since we are in the middle of error handling, and
// we don't want t() to cause further errors.
$message = new FormattableMarkup('%type: @message in %function (line %line of %file).', $error);
}
else {
// With verbose logging, we will also include a backtrace.
// 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.
$error['@backtrace'] = Error::formatBacktrace($backtrace);
$message = new FormattableMarkup('%type: @message in %function (line %line of %file). <pre class="backtrace">@backtrace</pre>', $error);
}
}
if ($fatal) {
// We fallback to a maintenance page at this point, because the page generation
// itself can generate errors.
// Should not translate the string to avoid errors producing more errors.
$message = 'The website encountered an unexpected error. Please try again later.' . '<br />' . $message;
if ($is_installer) {
// install_display_output() prints the output and ends script execution.
$output = [
'#title' => 'Error',
'#markup' => $message,
];
install_display_output($output, $GLOBALS['install_state'], $response->headers->all());
exit;
}
$response->setContent($message);
$response->setStatusCode(500, '500 Service unavailable (with message)');
$response->send();
// An exception must halt script execution.
exit;
}
if ($message) {
if (\Drupal::hasService('session')) {
// Message display is dependent on sessions being available.
\Drupal::messenger()->addMessage($message, $class, TRUE);
}
else {
print $message;
}
}
}
}
/**
* Returns the current error level.
*
* This function should only be used to get the current error level prior to the
* kernel being booted or before Drupal is installed. In all other situations
* the following code is preferred:
* @code
* \Drupal::config('system.logging')->get('error_level');
* @endcode
*
* @return string
* The current error level.
*/
function _drupal_get_error_level() {
// Raise the error level to maximum for the installer, so users are able to
// file proper bug reports for installer errors. The returned value is
// different to the one below, because the installer actually has a
// 'config.factory' service, which reads the default 'error_level' value from
// System module's default configuration and the default value is not verbose.
// @see error_displayable()
if (drupal_installation_attempted()) {
return ERROR_REPORTING_DISPLAY_VERBOSE;
}
$error_level = NULL;
// Try to get the error level configuration from database. If this fails,
// for example if the database connection is not there, try to read it from
// settings.php.
try {
$error_level = \Drupal::config('system.logging')->get('error_level');
}
catch (\Exception $e) {
$error_level = isset($GLOBALS['config']['system.logging']['error_level']) ? $GLOBALS['config']['system.logging']['error_level'] : ERROR_REPORTING_HIDE;
}
// If there is no container or if it has no config.factory service, we are
// possibly in an edge-case error situation while trying to serve a regular
// request on a public site, so use the non-verbose default value.
return $error_level ?: ERROR_REPORTING_DISPLAY_ALL;
}
/**
* Adds error information to headers so that tests can access it.
*
* @param $message
* The error message.
* @param $type
* The type of error.
* @param $function
* The function that emitted the error.
* @param $file
* The file that emitted the error.
* @param $line
* The line number in file that emitted the error.
*/
function _drupal_error_header($message, $type, $function, $file, $line) {
// $number does not use drupal_static as it should not be reset
// as it uniquely identifies each PHP error.
static $number = 0;
$assertion = [
$message,
$type,
[
'function' => $function,
'file' => $file,
'line' => $line,
],
];
// For non-fatal errors (e.g. PHP notices) _drupal_log_error can be called
// multiple times per request. In that case the response is typically
// generated outside of the error handler, e.g., in a controller. As a
// result it is not possible to use a Response object here but instead the
// headers need to be emitted directly.
header('X-Drupal-Assertion-' . $number . ': ' . rawurlencode(serialize($assertion)));
$number++;
}

File diff suppressed because it is too large Load diff

View file

@ -0,0 +1,962 @@
<?php
/**
* @file
* Functions for form and batch generation and processing.
*/
use Drupal\Component\Utility\UrlHelper;
use Drupal\Core\Render\Element;
use Drupal\Core\Render\Element\RenderElement;
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, ['id', 'name', 'size']);
RenderElement::setAttributes($element, ['form-select']);
$variables['attributes'] = $element['#attributes'];
$variables['options'] = form_select_options($element);
}
/**
* Converts an options form element into a structured array for output.
*
* 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 mixed[]
* A structured, possibly nested, array of options and optgroups for use in a
* select form element.
* - label: A translated string whose value is the text of a single HTML
* option element, or the label attribute for an optgroup.
* - options: Optional, array of options for an optgroup.
* - selected: A boolean that indicates whether the option is selected when
* rendered.
* - type: A string that defines the element type. The value can be 'option'
* or 'optgroup'.
* - value: A string that contains the value attribute for the option.
*/
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[] = [
'type' => 'optgroup',
'label' => $key,
'options' => form_select_options($element, $choice),
];
}
elseif (is_object($choice) && isset($choice->option)) {
$options = array_merge($options, form_select_options($element, $choice->option));
}
else {
$option = [];
$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)) {
$option['selected'] = TRUE;
}
else {
$option['selected'] = FALSE;
}
$option['type'] = 'option';
$option['value'] = $key;
$option['label'] = $choice;
$options[] = $option;
}
}
return $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 = [];
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, ['id']);
RenderElement::setAttributes($element);
$variables['attributes'] = isset($element['#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;
if (isset($element['#title']) && $element['#title'] !== '') {
$variables['legend']['title'] = ['#markup' => $element['#title']];
}
$variables['legend']['attributes'] = new Attribute();
// Add 'visually-hidden' class to legend span.
if ($variables['title_display'] == 'invisible') {
$variables['legend_span']['attributes'] = new Attribute(['class' => ['visually-hidden']]);
}
else {
$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;
}
// Suppress error messages.
$variables['errors'] = NULL;
}
/**
* 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, #description, #required,
* #summary_attributes, #title, #value.
*/
function template_preprocess_details(&$variables) {
$element = $variables['element'];
$variables['attributes'] = $element['#attributes'];
$variables['summary_attributes'] = new Attribute($element['#summary_attributes']);
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'] : '';
// If the element title is a string, wrap it a render array so that markup
// will not be escaped (but XSS-filtered).
if (is_string($variables['title']) && $variables['title'] !== '') {
$variables['title'] = ['#markup' => $variables['title']];
}
$variables['description'] = (!empty($element['#description'])) ? $element['#description'] : '';
$variables['children'] = (isset($element['#children'])) ? $element['#children'] : '';
$variables['value'] = (isset($element['#value'])) ? $element['#value'] : '';
$variables['required'] = !empty($element['#required']) ? $element['#required'] : NULL;
// Suppress error messages.
$variables['errors'] = NULL;
}
/**
* 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'] = [];
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'] = [];
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'];
// Remove name attribute if empty, for W3C compliance.
if (isset($variables['attributes']['name']) && empty((string) $variables['attributes']['name'])) {
unset($variables['attributes']['name']);
}
$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, ['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, #maxlength,
* #placeholder, #required, #attributes, #resizable.
*/
function template_preprocess_textarea(&$variables) {
$element = $variables['element'];
$attributes = ['id', 'name', 'rows', 'cols', 'maxlength', 'placeholder'];
Element::setAttributes($element, $attributes);
RenderElement::setAttributes($element, ['form-textarea']);
$variables['wrapper_attributes'] = new Attribute();
$variables['attributes'] = new Attribute($element['#attributes']);
$variables['value'] = $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 += [
'#title_display' => 'before',
'#wrapper_attributes' => [],
'#label_attributes' => [],
];
$variables['attributes'] = $element['#wrapper_attributes'];
// Add element #id for #type 'item'.
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;
// Suppress error messages.
$variables['errors'] = NULL;
// 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'] = ['#theme' => 'form_element_label'];
$variables['label'] += array_intersect_key($element, array_flip(['#id', '#required', '#title', '#title_display']));
$variables['label']['#attributes'] = $element['#label_attributes'];
$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.
if (isset($element['#title']) && $element['#title'] !== '') {
$variables['title'] = ['#markup' => $element['#title']];
}
// 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\Html::escape() 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 = \Drupal::entityTypeManager()->getStorage('node')
* ->loadByProperties(['uid' => $uid, 'type' => $type]);
* $node = reset($nodes);
* $context['results'][] = $node->id() . ' : ' . Html::escape($node->label());
* $context['message'] = Html::escape($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 . ' : ' . Html::escape($row->title);
* $context['sandbox']['progress']++;
* $context['sandbox']['current_id'] = $row->id;
* $context['message'] = Html::escape($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::messenger()->addMessage($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().
* - library: An array of batch-specific CSS and JS libraries.
* - url_options: options passed to the \Drupal\Core\Url object when
* constructing redirect URLs for the batch.
* - 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 = [
'sets' => [],
'has_form_submits' => FALSE,
];
}
// Base and default properties for the batch set.
$init = [
'sandbox' => [],
'results' => [],
'success' => FALSE,
'start' => 0,
'elapsed' => 0,
];
$defaults = [
'title' => t('Processing'),
'init_message' => t('Initializing.'),
'progress_message' => t('Completed @current of @total.'),
'error_message' => t('An error has occurred.'),
];
$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/>&nbsp;';
// 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, [$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. Note that to simply force a batch to (conditionally)
* redirect to a custom location after it is finished processing but to
* otherwise allow the standard form API batch handling to occur, it is not
* necessary to call batch_process() and use this parameter. Instead, make
* the batch 'finished' callback return an instance of
* \Symfony\Component\HttpFoundation\RedirectResponse, which will be used
* automatically by the standard batch processing pipeline (and which takes
* precedence over this parameter).
* User will be redirected to the page that started the batch if this argument
* is omitted and no redirect response was returned by the 'finished'
* callback. Any query arguments will be automatically persisted.
* @param \Drupal\Core\Url $url
* (optional) URL of the batch processing page. Should only be used for
* separate scripts like update.php.
* @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 = [
'current_set' => 0,
'progressive' => TRUE,
'url' => isset($url) ? $url : Url::fromRoute('system.batch_page.html'),
'source_url' => Url::fromRouteMatch(\Drupal::routeMatch())->mergeOptions(['query' => \Drupal::request()->query->all()]),
'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>', [':error_url' => $error_url->toString(TRUE)->getGeneratedUrl()]);
// 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 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(TRUE)->getGeneratedUrl());
}
}
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 = [];
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. The name and class of the queue are added by reference to the
* batch set.
*
* @param $batch
* The batch array.
* @param $set_id
* The id of the set to process.
*/
function _batch_populate_queue(&$batch, $set_id) {
$batch_set = &$batch['sets'][$set_id];
if (isset($batch_set['operations'])) {
$batch_set += [
'queue' => [
'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 = [];
}
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".
*/

File diff suppressed because it is too large Load diff

File diff suppressed because it is too large Load diff

View file

@ -0,0 +1,177 @@
<?php
/**
* @file
* API for the Drupal menu system.
*/
/**
* @addtogroup menu
* @{
*/
use Drupal\Component\Render\FormattableMarkup;
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 += [
'localized_options' => [],
];
$link_text = $link['title'];
if (!empty($variables['element']['#active'])) {
$variables['is_active'] = TRUE;
// Add text to indicate active tab for non-visual users.
$active = new FormattableMarkup('<span class="visually-hidden">@label</span>', ['@label' => t('(active tab)')]);
$link_text = t('@local-task-title@active', ['@local-task-title' => $link_text, '@active' => $active]);
}
$link['localized_options']['set_active_class'] = TRUE;
$variables['link'] = [
'#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 += [
'localized_options' => [],
];
$link['localized_options']['attributes']['class'][] = 'button';
$link['localized_options']['attributes']['class'][] = 'button-action';
$link['localized_options']['set_active_class'] = TRUE;
$variables['link'] = [
'#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 [
'tools' => 'Tools',
'admin' => 'Administration',
'account' => 'User account menu',
'main' => 'Main navigation',
'footer' => 'Footer menu',
];
}
/**
* Collects the local tasks (tabs) for the current route.
*
* @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.
* - route_name: The route name for the current page used to collect the local
* tasks.
*
* @see hook_menu_local_tasks_alter()
* @see https://www.drupal.org/node/2544940
*
* @deprecated in Drupal 8.0.0, will be removed before Drupal 9.0.0.
*/
function menu_local_tasks($level = 0) {
/** @var \Drupal\Core\Menu\LocalTaskManagerInterface $manager */
$manager = \Drupal::service('plugin.manager.menu.local_task');
return $manager->getLocalTasks(\Drupal::routeMatch()->getRouteName(), $level);
}
/**
* Returns the rendered local tasks at the top level.
*
* @see https://www.drupal.org/node/2874695
*
* @deprecated in Drupal 8.0.0, will be removed before Drupal 9.0.0.
*/
function menu_primary_local_tasks() {
/** @var \Drupal\Core\Menu\LocalTaskManagerInterface $manager */
$manager = \Drupal::service('plugin.manager.menu.local_task');
$links = $manager->getLocalTasks(\Drupal::routeMatch()->getRouteName(), 0);
// Do not display single tabs.
return count(Element::getVisibleChildren($links['tabs'])) > 1 ? $links['tabs'] : '';
}
/**
* Returns the rendered local tasks at the second level.
*
* @see https://www.drupal.org/node/2874695
*
* @deprecated in Drupal 8.0.0, will be removed before Drupal 9.0.0.
*/
function menu_secondary_local_tasks() {
/** @var \Drupal\Core\Menu\LocalTaskManagerInterface $manager */
$manager = \Drupal::service('plugin.manager.menu.local_task');
$links = $manager->getLocalTasks(\Drupal::routeMatch()->getRouteName(), 1);
// Do not display single tabs.
return count(Element::getVisibleChildren($links['tabs'])) > 1 ? $links['tabs'] : '';
}
/**
* Returns a renderable element for the primary and secondary tabs.
*/
function menu_local_tabs() {
$build = [
'#theme' => 'menu_local_tasks',
'#primary' => menu_primary_local_tasks(),
'#secondary' => menu_secondary_local_tasks(),
];
return !empty($build['#primary']) || !empty($build['#secondary']) ? $build : [];
}
/**
* Clears all cached menu data.
*
* This should be called any time broad changes
* might have been made to the router items or menu links.
*
* @deprecated in Drupal 8.6.0, will be removed before Drupal 9.0.0. Use
* \Drupal::cache('menu')->invalidateAll() instead.
*
* @see https://www.drupal.org/node/2989138
*/
function menu_cache_clear_all() {
@trigger_error("menu_cache_clear_all() is deprecated in Drupal 8.6.0 and will be removed before Drupal 9.0.0. Use \Drupal::cache('menu')->invalidateAll() instead. See https://www.drupal.org/node/2989138", E_USER_DEPRECATED);
\Drupal::cache('menu')->invalidateAll();
}
/**
* @} End of "addtogroup menu".
*/

View file

@ -0,0 +1,235 @@
<?php
/**
* @file
* API for loading and interacting with Drupal modules.
*/
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 = [
'theme' => [],
'filepaths' => [],
];
// ThemeHandler maintains the 'system.theme.data' state record.
$theme_data = \Drupal::state()->get('system.theme.data', []);
foreach ($theme_data as $name => $theme) {
$lists['theme'][$name] = $theme;
$lists['filepaths'][] = [
'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::service('extension.list.module')->reset();
\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 = [];
// 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\ModuleInstaller::install()
$module_handler = \Drupal::moduleHandler();
$current_module_filenames = $module_handler->getModuleList();
$current_modules = array_fill_keys(array_keys($current_module_filenames), 0);
$current_modules = module_config_sort(array_merge($current_modules, $extension_config->get('module')));
$module_filenames = [];
foreach ($current_modules as $name => $weight) {
$module_filenames[$name] = $current_module_filenames[$name];
}
// Update the module list in the extension handler.
$module_handler->setModuleList($module_filenames);
return;
}
}
/**
* Sorts the configured list of enabled modules.
*
* The list of enabled modules is expected to be ordered by weight and name.
* The list is always sorted on write to avoid the overhead on read.
*
* @param array $data
* An array of module configuration data.
*
* @return array
* An array of module configuration data sorted by weight and name.
*/
function module_config_sort($data) {
// PHP array sorting functions such as uasort() do not work with both keys and
// values at the same time, so we achieve weight and name sorting by computing
// strings with both information concatenated (weight first, name second) and
// use that as a regular string sort reference list via array_multisort(),
// compound of "[sign-as-integer][padded-integer-weight][name]"; e.g., given
// two modules and weights (spaces added for clarity):
// - Block with weight -5: 0 0000000000000000005 block
// - Node with weight 0: 1 0000000000000000000 node
$sort = [];
foreach ($data as $name => $weight) {
// Prefix negative weights with 0, positive weights with 1.
// +/- signs cannot be used, since + (ASCII 43) is before - (ASCII 45).
$prefix = (int) ($weight >= 0);
// The maximum weight is PHP_INT_MAX, so pad all weights to 19 digits.
$sort[] = $prefix . sprintf('%019d', abs($weight)) . $name;
}
array_multisort($sort, SORT_STRING, $data);
return $data;
}

View file

@ -0,0 +1,331 @@
<?php
/**
* @file
* Functions to aid in presenting database results as a set of pages.
*/
use Drupal\Component\Utility\UrlHelper;
/**
* Returns the current page being requested for display within a pager.
*
* @param int $element
* (optional) An integer to distinguish between multiple pagers on one page.
*
* @return int
* 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 user
* 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.
*
* This function sets up the necessary global variables so that the render
* system will correctly process #type 'pager' render arrays to output pagers
* that correspond 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 controller
* 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 the items for the current page and put them into a
* // render array.
* $offset = $num_per_page * $page;
* $result = mymodule_select("SELECT * FROM data " . $where . " LIMIT %d, %d", $offset, $num_per_page)->fetchAll();
* $render = [];
* $render[] = [
* '#theme' => 'mymodule_results',
* '#result' => $result,
* ];
*
* // Finally, add the pager to the render array, and return.
* $render[] = ['#type' => 'pager'];
* return $render;
* @endcode
*
* A second example involves a controller 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);
*
* // Create a render array with the search results.
* $render = [];
* $render[] = [
* '#theme' => 'search_results',
* '#results' => $result->data,
* '#type' => 'remote',
* ];
*
* // Finally, add the pager to the render array, and return.
* $render[] = ['#type' => 'pager'];
* return $render;
* @endcode
*
* @param int $total
* The total number of items to be paged.
* @param int $limit
* The number of items the calling code will display per page.
* @param int $element
* (optional) An integer to distinguish between multiple pagers on one page.
*
* @return int
* 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 array
* 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(), ['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.
* - #route_parameters: An associative array of the route parameters.
* - #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'];
$route_name = $variables['pager']['#route_name'];
$route_parameters = isset($variables['pager']['#route_parameters']) ? $variables['pager']['#route_parameters'] : [];
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'] = [];
$options = [
'query' => pager_query_add_page($parameters, $element, 0),
];
$items['first']['href'] = \Drupal::url($route_name, $route_parameters, $options);
if (isset($tags[0])) {
$items['first']['text'] = $tags[0];
}
$items['previous'] = [];
$options = [
'query' => pager_query_add_page($parameters, $element, $pager_page_array[$element] - 1),
];
$items['previous']['href'] = \Drupal::url($route_name, $route_parameters, $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 = [
'query' => pager_query_add_page($parameters, $element, $i - 1),
];
$items['pages'][$i]['href'] = \Drupal::url($route_name, $route_parameters, $options);
if ($i == $pager_current) {
$variables['current'] = $i;
}
}
// Add an ellipsis if there are further next pages.
if ($i < $pager_max + 1) {
$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'] = [];
$options = [
'query' => pager_query_add_page($parameters, $element, $pager_page_array[$element] + 1),
];
$items['next']['href'] = \Drupal::url($route_name, $route_parameters, $options);
if (isset($tags[3])) {
$items['next']['text'] = $tags[3];
}
$items['last'] = [];
$options = [
'query' => pager_query_add_page($parameters, $element, $pager_max - 1),
];
$items['last']['href'] = \Drupal::url($route_name, $route_parameters, $options);
if (isset($tags[4])) {
$items['last']['text'] = $tags[4];
}
}
$variables['items'] = $items;
// The rendered link needs to play well with any other query parameter used
// on the page, like exposed filters, so for the cacheability all query
// parameters matter.
$variables['#cache']['contexts'][] = 'url.query_args';
}
/**
* Gets the URL query parameter array of a pager link.
*
* Adds to or adjusts the 'page' URL query parameter so that if you follow the
* link, you'll get page $index for pager $element on the page.
*
* The 'page' URL query parameter is a comma-delimited string, where each value
* is the target content page for the corresponding pager $element. For
* instance, if we have 5 pagers on a single page, and we want to have a link
* to a page that should display the 6th content page for the 3rd pager, and
* the 1st content page for all the other pagers, then the URL query will look
* like this: ?page=0,0,5,0,0 (page numbering starts at zero).
*
* @param array $query
* An associative array of URL query parameters to add to.
* @param int $element
* An integer to distinguish between multiple pagers on one page.
* @param int $index
* The index of the target page, for the given element, in the pager array.
*
* @return array
* The altered $query parameter array.
*/
function pager_query_add_page(array $query, $element, $index) {
global $pager_page_array;
// Build the 'page' query parameter. This is built based on the current
// page of each pager element (or NULL if the pager is not set), with the
// exception of the requested page index for the current element.
$max_element = max(array_keys($pager_page_array));
$element_pages = [];
for ($i = 0; $i <= $max_element; $i++) {
$element_pages[] = ($i == $element) ? $index : (isset($pager_page_array[$i]) ? $pager_page_array[$i] : NULL);
}
$query['page'] = implode(',', $element_pages);
// 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;
}

View file

@ -0,0 +1,237 @@
<?php
/**
* @file
* Schema API handling functions.
*/
/**
* @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 = [];
foreach (\Drupal::moduleHandler()->getModuleList() as $loaded_module => $filename) {
$updates[$loaded_module] = [];
}
// 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) {
$versions = &drupal_static(__FUNCTION__, []);
if ($reset) {
$versions = [];
}
if (!$versions) {
if (!$versions = \Drupal::keyValue('system.schema')->getAll()) {
$versions = [];
}
}
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) {
\Drupal::database()->schema()->createTable($name, $table);
}
}
/**
* Removes all tables defined in a module's hook_schema().
*
* @param string $module
* The module for which the tables will be removed.
*/
function drupal_uninstall_schema($module) {
$tables = drupal_get_module_schema($module);
_drupal_schema_initialize($tables, $module, FALSE);
$schema = \Drupal::database()->schema();
foreach ($tables as $table) {
if ($schema->tableExists($table['name'])) {
$schema->dropTable($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 [];
}
elseif (!empty($schema)) {
return $schema;
}
return [];
}
/**
* 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 data types.
*
* 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".
*/

View file

@ -0,0 +1,149 @@
<?php
/**
* @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.
*/
use Drupal\Component\Render\FormattableMarkup;
use Drupal\Core\Url;
use Drupal\Component\Utility\UrlHelper;
/**
* 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', ['@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 = [
'#theme' => 'tablesort_indicator',
'#style' => $ts['sort'],
];
$image = \Drupal::service('renderer')->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(new FormattableMarkup('@cell_content@image', ['@cell_content' => $cell_content, '@image' => $image]), new Url('<current>', [], [
'attributes' => ['title' => $title],
'query' => array_merge($ts['query'], [
'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(), ['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 = ['data' => $default];
}
}
$default += ['data' => NULL, 'field' => NULL];
return ['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';
}

File diff suppressed because it is too large Load diff

View file

@ -0,0 +1,136 @@
<?php
/**
* @file
* Theming for maintenance pages.
*/
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';
// 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.
// @todo This is just a workaround. Find a better way how to handle themes
// on maintenance pages, see https://www.drupal.org/node/2322619.
// This code is basically a duplicate of
// \Drupal\Core\Theme\ThemeInitialization::getActiveThemeByName.
$base_themes = [];
$ancestor = $theme;
while ($ancestor && isset($themes[$ancestor]->base_theme)) {
$base_themes[] = $themes[$themes[$ancestor]->base_theme];
$ancestor = $themes[$ancestor]->base_theme;
if ($ancestor) {
// Ensure that the base theme is added and installed.
$theme_handler->addTheme($themes[$ancestor]);
}
}
\Drupal::theme()->setActiveTheme($theme_init->getActiveTheme($themes[$custom_theme], $base_themes));
// Prime the theme registry.
Drupal::service('theme.registry');
}
/**
* Prepares variables for authorize.php operation report templates.
*
* This report displays the results of an operation run via authorize.php.
*
* Default template: authorize-report.html.twig.
*
* @param array $variables
* An associative array containing:
* - messages: An array of result messages.
*/
function template_preprocess_authorize_report(&$variables) {
$messages = [];
if (!empty($variables['messages'])) {
foreach ($variables['messages'] as $heading => $logs) {
$items = [];
foreach ($logs as $number => $log_message) {
if ($number === '#abort') {
continue;
}
$class = 'authorize-results__' . ($log_message['success'] ? 'success' : 'failure');
$items[] = [
'#wrapper_attributes' => ['class' => [$class]],
'#markup' => $log_message['message'],
];
}
$messages[] = [
'#theme' => 'item_list',
'#items' => $items,
'#title' => $heading,
];
}
}
$variables['messages'] = $messages;
}

View file

@ -0,0 +1,126 @@
<?php
/**
* @file
* Provides Unicode-related conversions and operations.
*/
use Drupal\Component\Utility\Unicode;
/**
* Returns Unicode library status and errors.
*/
/**
* Moves unicode_requirements() logic to system_requirements().
*
* @deprecated in Drupal 8.4.0 and will be removed before Drupal 9.0.0.
*
* @see https://www.drupal.org/node/2884698
*/
function unicode_requirements() {
@trigger_error('unicode_requirements() is deprecated in Drupal 8.4.0 and will be removed before Drupal 9.0.0. There is no replacement; system_requirements() now includes the logic instead. See https://www.drupal.org/node/2884698', E_USER_DEPRECATED);
$libraries = [
Unicode::STATUS_SINGLEBYTE => t('Standard PHP'),
Unicode::STATUS_MULTIBYTE => t('PHP Mbstring Extension'),
Unicode::STATUS_ERROR => t('Error'),
];
$severities = [
Unicode::STATUS_SINGLEBYTE => REQUIREMENT_WARNING,
Unicode::STATUS_MULTIBYTE => NULL,
Unicode::STATUS_ERROR => REQUIREMENT_ERROR,
];
$failed_check = Unicode::check();
$library = Unicode::getStatus();
$requirements['unicode'] = [
'title' => t('Unicode library'),
'value' => $libraries[$library],
'severity' => $severities[$library],
];
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="http://php.net/mbstring">PHP mbstring extension</a> for improved Unicode support.');
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="http://php.net/mbstring">PHP mbstring documentation</a> for more information.');
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="http://php.net/mbstring">PHP mbstring documentation</a> for more information.');
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="http://php.net/mbstring">PHP mbstring documentation</a> for more information.');
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="http://php.net/mbstring">PHP mbstring documentation</a> for more information.');
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
*
* @deprecated in Drupal 8.3.0 and will bre removed in Drupal 9.0.0. Use
* xml_parser_create() and
* xml_parser_set_option($xml_parser, XML_OPTION_TARGET_ENCODING, 'utf-8')
* instead.
*/
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 = ['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.', ['%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;
}

View file

@ -0,0 +1,688 @@
<?php
/**
* @file
* Drupal database update API.
*
* This file contains functions to perform database updates for a Drupal
* installation. It is included and used extensively by update.php.
*/
use Drupal\Component\Graph\Graph;
use Drupal\Core\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 (['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 = [];
$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'] += [
'value' => 'The installed schema version meets the minimum.',
'description' => 'Schema version: ' . $system_schema,
];
}
else {
$requirements['minimum schema'] += [
'value' => 'The installed schema version does not meet the minimum.',
'severity' => REQUIREMENT_ERROR,
'description' => 'Your system schema version is ' . $system_schema . '. Updating directly from a schema version prior to 8000 is not supported. You must upgrade your site to Drupal 8 first, see https://www.drupal.org/docs/8/upgrade.',
];
}
return $requirements;
}
/**
* Checks update requirements and reports errors and (optionally) warnings.
*/
function update_check_requirements() {
// Check requirements of all loaded modules.
$requirements = \Drupal::moduleHandler()->invokeAll('requirements', ['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, [$function]))) {
return;
}
$ret = [];
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.
// @see https://www.drupal.org/node/2564311
catch (Exception $e) {
watchdog_exception('update', $e);
$variables = Error::decodeException($e);
unset($variables['backtrace']);
$ret['#abort'] = ['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] = [];
}
if (!isset($context['results'][$module][$number])) {
$context['results'][$module][$number] = [];
}
$context['results'][$module][$number] = array_merge($context['results'][$module][$number], $ret);
if (!empty($ret['#abort'])) {
// Record this function in the list of updates that were aborted.
$context['results']['#abort'][] = $function;
}
// Record the schema update if it was completed successfully.
if ($context['finished'] == 1 && empty($ret['#abort'])) {
drupal_set_installed_schema_version($module, $number);
}
$context['message'] = t('Updating @module', ['@module' => $module]);
}
/**
* Executes a single hook_post_update_NAME().
*
* @param string $function
* The function name, that should be executed.
* @param array $context
* The batch context array.
*/
function update_invoke_post_update($function, &$context) {
$ret = [];
// If this update was aborted in a previous step, or has a dependency that was
// aborted in a previous step, go no further.
if (!empty($context['results']['#abort'])) {
return;
}
list($module, $name) = explode('_post_update_', $function, 2);
module_load_include('php', $module, $module . '.post_update');
if (function_exists($function)) {
try {
$ret['results']['query'] = $function($context['sandbox']);
$ret['results']['success'] = TRUE;
if (!isset($context['sandbox']['#finished']) || (isset($context['sandbox']['#finished']) && $context['sandbox']['#finished'] >= 1)) {
\Drupal::service('update.post_update_registry')->registerInvokedUpdates([$function]);
}
}
// @TODO We may want to do different error handling for different exception
// types, but for now we'll just log the exception and return the message
// for printing.
// @see https://www.drupal.org/node/2564311
catch (Exception $e) {
watchdog_exception('update', $e);
$variables = Error::decodeException($e);
unset($variables['backtrace']);
$ret['#abort'] = [
'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][$name])) {
$context['results'][$module][$name] = [];
}
$context['results'][$module][$name] = array_merge($context['results'][$module][$name], $ret);
if (!empty($ret['#abort'])) {
// Record this function in the list of updates that were aborted.
$context['results']['#abort'][] = $function;
}
$context['message'] = t('Post updating @module', ['@module' => $module]);
}
/**
* 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 = ['system' => []];
$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;
}
foreach ($updates as $update) {
if ($update == \Drupal::CORE_MINIMUM_SCHEMA_VERSION) {
$ret[$module]['warning'] = '<em>' . $module . '</em> module cannot be updated. It contains an update numbered as ' . \Drupal::CORE_MINIMUM_SCHEMA_VERSION . ' which is reserved for the earliest installation of a module in Drupal ' . \Drupal::CORE_COMPATIBILITY . ', before any updates. In order to update <em>' . $module . '</em> module, you will need to 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(["\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, ['Drupal\Component\Utility\SortArray', 'sortByWeightElement']);
foreach ($graph as $function => &$data) {
$module = $data['module'];
$number = $data['number'];
// If the update function is missing and has not yet been performed, mark
// it and everything that ultimately depends on it as disallowed.
if (update_is_missing($module, $number, $update_functions) && !update_already_performed($module, $number)) {
$data['allowed'] = FALSE;
foreach (array_keys($data['paths']) as $dependent) {
$graph[$dependent]['allowed'] = FALSE;
$graph[$dependent]['missing_dependencies'][] = $function;
}
}
elseif (!isset($data['allowed'])) {
$data['allowed'] = TRUE;
$data['missing_dependencies'] = [];
}
// Now that we have finished processing this function, remove it from the
// graph if it was not part of the original list. This ensures that we
// never try to run any updates that were not specifically requested.
if (!isset($update_functions[$module][$number])) {
unset($graph[$function]);
}
}
return $graph;
}
/**
* Returns an organized list of update functions for a set of modules.
*
* @param $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 = [];
foreach ($starting_updates as $module => $version) {
$update_functions[$module] = [];
$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 = [];
// 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\Core\Extension\ModuleHandlerInterface::invokeAll()
* @see hook_update_dependencies()
*/
function update_retrieve_dependencies() {
$return = [];
// 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)) {
$updated_dependencies = $function();
// Each implementation of hook_update_dependencies() returns a
// multidimensional, associative array containing some keys that
// represent module names (which are strings) and other keys that
// represent update function numbers (which are integers). We cannot use
// array_merge_recursive() to properly merge these results, since it
// treats strings and integers differently. Therefore, we have to
// explicitly loop through the expected array structure here and perform
// the merge manually.
if (isset($updated_dependencies) && is_array($updated_dependencies)) {
foreach ($updated_dependencies as $module_name => $module_data) {
foreach ($module_data as $update_version => $update_data) {
foreach ($update_data as $module_dependency => $update_dependency) {
// If there are redundant dependencies declared for the same
// update function (so that it is declared to depend on more than
// one update from a particular module), record the dependency on
// the highest numbered update here, since that automatically
// implies the previous ones. For example, if one module's
// implementation of hook_update_dependencies() required this
// ordering:
//
// system_update_8002 ---> user_update_8001
//
// but another module's implementation of the hook required this
// one:
//
// system_update_8003 ---> user_update_8001
//
// we record the second one, since system_update_8002() is always
// guaranteed to run before system_update_8003() anyway (within
// an individual module, updates are always run in numerical
// order).
if (!isset($return[$module_name][$update_version][$module_dependency]) || $update_dependency > $return[$module_name][$update_version][$module_dependency]) {
$return[$module_name][$update_version][$module_dependency] = $update_dependency;
}
}
}
}
}
}
}
return $return;
}
/**
* 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') ?: [];
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();
}
}

View file

@ -0,0 +1,58 @@
<?php
/**
* @file
* Miscellaneous functions.
*/
use Drupal\Core\PhpStorage\PhpStorageFactory;
use Drupal\Core\Cache\Cache;
use Drupal\Core\DrupalKernel;
use Symfony\Component\HttpFoundation\Request;
/**
* Rebuilds all caches even when Drupal itself does not work.
*
* @param $class_loader
* The class loader. Normally Composer's ClassLoader, as included by the
* front controller, but may also be decorated; e.g.,
* \Symfony\Component\ClassLoader\ApcClassLoader, \Symfony\Component\ClassLoader\WinCacheClassLoader, or \Symfony\Component\ClassLoader\XcacheClassLoader
* @param \Symfony\Component\HttpFoundation\Request $request
* The current request.
*
* @see rebuild.php
*/
function drupal_rebuild($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 php cache.
PhpStorageFactory::get('twig')->deleteAll();
// Bootstrap up to where caches exist and clear them.
$kernel = new DrupalKernel('prod', $class_loader);
$kernel->setSitePath(DrupalKernel::findSitePath($request));
// Invalidate the container.
$kernel->invalidateContainer();
// Prepare a NULL 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');
}