Drupal 8.0.0 beta 12. More info: https://www.drupal.org/node/2514176
This commit is contained in:
commit
9921556621
13277 changed files with 1459781 additions and 0 deletions
189
core/lib/Drupal/Core/Utility/Error.php
Normal file
189
core/lib/Drupal/Core/Utility/Error.php
Normal file
|
@ -0,0 +1,189 @@
|
|||
<?php
|
||||
|
||||
/**
|
||||
* @file
|
||||
* Contains \Drupal\Core\Utility\Error.
|
||||
*/
|
||||
|
||||
namespace Drupal\Core\Utility;
|
||||
|
||||
use Drupal\Component\Utility\SafeMarkup;
|
||||
use Drupal\Component\Utility\Xss;
|
||||
use Drupal\Core\Database\DatabaseExceptionWrapper;
|
||||
|
||||
/**
|
||||
* Drupal error utility class.
|
||||
*/
|
||||
class Error {
|
||||
|
||||
/**
|
||||
* The error severity level.
|
||||
*
|
||||
* @var int
|
||||
*/
|
||||
const ERROR = 3;
|
||||
|
||||
/**
|
||||
* An array of blacklisted functions.
|
||||
*
|
||||
* @var array
|
||||
*/
|
||||
protected static $blacklistFunctions = array('debug', '_drupal_error_handler', '_drupal_exception_handler');
|
||||
|
||||
/**
|
||||
* Decodes an exception and retrieves the correct caller.
|
||||
*
|
||||
* @param \Exception|\BaseException $exception
|
||||
* The exception object that was thrown.
|
||||
*
|
||||
* @return array
|
||||
* An error in the format expected by _drupal_log_error().
|
||||
*/
|
||||
public static function decodeException($exception) {
|
||||
$message = $exception->getMessage();
|
||||
|
||||
$backtrace = $exception->getTrace();
|
||||
// Add the line throwing the exception to the backtrace.
|
||||
array_unshift($backtrace, array('line' => $exception->getLine(), 'file' => $exception->getFile()));
|
||||
|
||||
// For PDOException errors, we try to return the initial caller,
|
||||
// skipping internal functions of the database layer.
|
||||
if ($exception instanceof \PDOException || $exception instanceof DatabaseExceptionWrapper) {
|
||||
// The first element in the stack is the call, the second element gives us
|
||||
// the caller. We skip calls that occurred in one of the classes of the
|
||||
// database layer or in one of its global functions.
|
||||
$db_functions = array('db_query', 'db_query_range');
|
||||
while (!empty($backtrace[1]) && ($caller = $backtrace[1]) &&
|
||||
((isset($caller['class']) && (strpos($caller['class'], 'Query') !== FALSE || strpos($caller['class'], 'Database') !== FALSE || strpos($caller['class'], 'PDO') !== FALSE)) ||
|
||||
in_array($caller['function'], $db_functions))) {
|
||||
// We remove that call.
|
||||
array_shift($backtrace);
|
||||
}
|
||||
if (isset($exception->query_string, $exception->args)) {
|
||||
$message .= ": " . $exception->query_string . "; " . print_r($exception->args, TRUE);
|
||||
}
|
||||
}
|
||||
|
||||
$caller = static::getLastCaller($backtrace);
|
||||
|
||||
return array(
|
||||
'%type' => get_class($exception),
|
||||
// The standard PHP exception handler considers that the exception message
|
||||
// is plain-text. We mimic this behavior here.
|
||||
'!message' => SafeMarkup::checkPlain($message),
|
||||
'%function' => $caller['function'],
|
||||
'%file' => $caller['file'],
|
||||
'%line' => $caller['line'],
|
||||
'severity_level' => static::ERROR,
|
||||
'backtrace' => $backtrace,
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Renders an exception error message without further exceptions.
|
||||
*
|
||||
* @param \Exception|\BaseException $exception
|
||||
* The exception object that was thrown.
|
||||
*
|
||||
* @return string
|
||||
* An error message.
|
||||
*/
|
||||
public static function renderExceptionSafe($exception) {
|
||||
$decode = static::decodeException($exception);
|
||||
$backtrace = $decode['backtrace'];
|
||||
unset($decode['backtrace']);
|
||||
// Remove 'main()'.
|
||||
array_shift($backtrace);
|
||||
|
||||
$output = SafeMarkup::format('%type: !message in %function (line %line of %file).', $decode);
|
||||
// Even though it is possible that this method is called on a public-facing
|
||||
// site, it is only called when the exception handler itself threw an
|
||||
// exception, which normally means that a code change caused the system to
|
||||
// no longer function correctly (as opposed to a user-triggered error), so
|
||||
// we assume that it is safe to include a verbose backtrace.
|
||||
$output .= '<pre>' . static::formatBacktrace($backtrace) . '</pre>';
|
||||
return SafeMarkup::set($output);
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the last caller from a backtrace.
|
||||
*
|
||||
* @param array $backtrace
|
||||
* A standard PHP backtrace. Passed by reference.
|
||||
*
|
||||
* @return array
|
||||
* An associative array with keys 'file', 'line' and 'function'.
|
||||
*/
|
||||
public static function getLastCaller(array &$backtrace) {
|
||||
// Errors that occur inside PHP internal functions do not generate
|
||||
// information about file and line. Ignore black listed functions.
|
||||
while (($backtrace && !isset($backtrace[0]['line'])) ||
|
||||
(isset($backtrace[1]['function']) && in_array($backtrace[1]['function'], static::$blacklistFunctions))) {
|
||||
array_shift($backtrace);
|
||||
}
|
||||
|
||||
// The first trace is the call itself.
|
||||
// It gives us the line and the file of the last call.
|
||||
$call = $backtrace[0];
|
||||
|
||||
// The second call gives us the function where the call originated.
|
||||
if (isset($backtrace[1])) {
|
||||
if (isset($backtrace[1]['class'])) {
|
||||
$call['function'] = $backtrace[1]['class'] . $backtrace[1]['type'] . $backtrace[1]['function'] . '()';
|
||||
}
|
||||
else {
|
||||
$call['function'] = $backtrace[1]['function'] . '()';
|
||||
}
|
||||
}
|
||||
else {
|
||||
$call['function'] = 'main()';
|
||||
}
|
||||
|
||||
return $call;
|
||||
}
|
||||
|
||||
/**
|
||||
* Formats a backtrace into a plain-text string.
|
||||
*
|
||||
* The calls show values for scalar arguments and type names for complex ones.
|
||||
*
|
||||
* @param array $backtrace
|
||||
* A standard PHP backtrace.
|
||||
*
|
||||
* @return string
|
||||
* A plain-text line-wrapped string ready to be put inside <pre>.
|
||||
*/
|
||||
public static function formatBacktrace(array $backtrace) {
|
||||
$return = '';
|
||||
|
||||
foreach ($backtrace as $trace) {
|
||||
$call = array('function' => '', 'args' => array());
|
||||
|
||||
if (isset($trace['class'])) {
|
||||
$call['function'] = $trace['class'] . $trace['type'] . $trace['function'];
|
||||
}
|
||||
elseif (isset($trace['function'])) {
|
||||
$call['function'] = $trace['function'];
|
||||
}
|
||||
else {
|
||||
$call['function'] = 'main';
|
||||
}
|
||||
|
||||
if (isset($trace['args'])) {
|
||||
foreach ($trace['args'] as $arg) {
|
||||
if (is_scalar($arg)) {
|
||||
$call['args'][] = is_string($arg) ? '\'' . Xss::filter($arg) . '\'' : $arg;
|
||||
}
|
||||
else {
|
||||
$call['args'][] = ucfirst(gettype($arg));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
$return .= $call['function'] . '(' . implode(', ', $call['args']) . ")\n";
|
||||
}
|
||||
|
||||
return $return;
|
||||
}
|
||||
|
||||
}
|
151
core/lib/Drupal/Core/Utility/LinkGenerator.php
Normal file
151
core/lib/Drupal/Core/Utility/LinkGenerator.php
Normal file
|
@ -0,0 +1,151 @@
|
|||
<?php
|
||||
|
||||
/**
|
||||
* @file
|
||||
* Contains \Drupal\Core\Utility\LinkGenerator.
|
||||
*/
|
||||
|
||||
namespace Drupal\Core\Utility;
|
||||
|
||||
use Drupal\Component\Serialization\Json;
|
||||
use Drupal\Component\Utility\SafeMarkup;
|
||||
use Drupal\Core\Extension\ModuleHandlerInterface;
|
||||
use Drupal\Core\GeneratedLink;
|
||||
use Drupal\Core\Link;
|
||||
use Drupal\Core\Path\AliasManagerInterface;
|
||||
use Drupal\Core\Routing\UrlGeneratorInterface;
|
||||
use Drupal\Core\Template\Attribute;
|
||||
use Drupal\Core\Url;
|
||||
|
||||
/**
|
||||
* Provides a class which generates a link with route names and parameters.
|
||||
*/
|
||||
class LinkGenerator implements LinkGeneratorInterface {
|
||||
|
||||
/**
|
||||
* The url generator.
|
||||
*
|
||||
* @var \Drupal\Core\Routing\UrlGeneratorInterface
|
||||
*/
|
||||
protected $urlGenerator;
|
||||
|
||||
/**
|
||||
* The module handler firing the route_link alter hook.
|
||||
*
|
||||
* @var \Drupal\Core\Extension\ModuleHandlerInterface
|
||||
*/
|
||||
protected $moduleHandler;
|
||||
|
||||
/**
|
||||
* Constructs a LinkGenerator instance.
|
||||
*
|
||||
* @param \Drupal\Core\Routing\UrlGeneratorInterface $url_generator
|
||||
* The url generator.
|
||||
* @param \Drupal\Core\Extension\ModuleHandlerInterface $module_handler
|
||||
* The module handler.
|
||||
*/
|
||||
public function __construct(UrlGeneratorInterface $url_generator, ModuleHandlerInterface $module_handler) {
|
||||
$this->urlGenerator = $url_generator;
|
||||
$this->moduleHandler = $module_handler;
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function generateFromLink(Link $link, $collect_cacheability_metadata = FALSE) {
|
||||
return $this->generate($link->getText(), $link->getUrl(), $collect_cacheability_metadata);
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*
|
||||
* For anonymous users, the "active" class will be calculated on the server,
|
||||
* because most sites serve each anonymous user the same cached page anyway.
|
||||
* For authenticated users, the "active" class will be calculated on the
|
||||
* client (through JavaScript), only data- attributes are added to links to
|
||||
* prevent breaking the render cache. The JavaScript is added in
|
||||
* system_page_attachments().
|
||||
*
|
||||
* @see system_page_attachments()
|
||||
*/
|
||||
public function generate($text, Url $url, $collect_cacheability_metadata = FALSE) {
|
||||
// Performance: avoid Url::toString() needing to retrieve the URL generator
|
||||
// service from the container.
|
||||
$url->setUrlGenerator($this->urlGenerator);
|
||||
|
||||
// Start building a structured representation of our link to be altered later.
|
||||
$variables = array(
|
||||
// @todo Inject the service when drupal_render() is converted to one.
|
||||
'text' => is_array($text) ? drupal_render($text) : $text,
|
||||
'url' => $url,
|
||||
'options' => $url->getOptions(),
|
||||
);
|
||||
|
||||
// Merge in default options.
|
||||
$variables['options'] += array(
|
||||
'attributes' => array(),
|
||||
'query' => array(),
|
||||
'language' => NULL,
|
||||
'set_active_class' => FALSE,
|
||||
'absolute' => FALSE,
|
||||
);
|
||||
|
||||
// Add a hreflang attribute if we know the language of this link's url and
|
||||
// hreflang has not already been set.
|
||||
if (!empty($variables['options']['language']) && !isset($variables['options']['attributes']['hreflang'])) {
|
||||
$variables['options']['attributes']['hreflang'] = $variables['options']['language']->getId();
|
||||
}
|
||||
|
||||
// Set the "active" class if the 'set_active_class' option is not empty.
|
||||
if (!empty($variables['options']['set_active_class']) && !$url->isExternal()) {
|
||||
// Add a "data-drupal-link-query" attribute to let the
|
||||
// drupal.active-link library know the query in a standardized manner.
|
||||
if (!empty($variables['options']['query'])) {
|
||||
$query = $variables['options']['query'];
|
||||
ksort($query);
|
||||
$variables['options']['attributes']['data-drupal-link-query'] = Json::encode($query);
|
||||
}
|
||||
|
||||
// Add a "data-drupal-link-system-path" attribute to let the
|
||||
// drupal.active-link library know the path in a standardized manner.
|
||||
if ($url->isRouted() && !isset($variables['options']['attributes']['data-drupal-link-system-path'])) {
|
||||
// @todo System path is deprecated - use the route name and parameters.
|
||||
$system_path = $url->getInternalPath();
|
||||
// Special case for the front page.
|
||||
$variables['options']['attributes']['data-drupal-link-system-path'] = $system_path == '' ? '<front>' : $system_path;
|
||||
}
|
||||
}
|
||||
|
||||
// Remove all HTML and PHP tags from a tooltip, calling expensive strip_tags()
|
||||
// only when a quick strpos() gives suspicion tags are present.
|
||||
if (isset($variables['options']['attributes']['title']) && strpos($variables['options']['attributes']['title'], '<') !== FALSE) {
|
||||
$variables['options']['attributes']['title'] = strip_tags($variables['options']['attributes']['title']);
|
||||
}
|
||||
|
||||
// Allow other modules to modify the structure of the link.
|
||||
$this->moduleHandler->alter('link', $variables);
|
||||
|
||||
// Move attributes out of options since generateFromRoute() doesn't need
|
||||
// them. Include a placeholder for the href.
|
||||
$attributes = array('href' => '') + $variables['options']['attributes'];
|
||||
unset($variables['options']['attributes']);
|
||||
$url->setOptions($variables['options']);
|
||||
|
||||
if (!$collect_cacheability_metadata) {
|
||||
$url_string = $url->toString($collect_cacheability_metadata);
|
||||
}
|
||||
else {
|
||||
$generated_url = $url->toString($collect_cacheability_metadata);
|
||||
$url_string = $generated_url->getGeneratedUrl();
|
||||
$generated_link = GeneratedLink::createFromObject($generated_url);
|
||||
}
|
||||
// The result of the URL generator is a plain-text URL to use as the href
|
||||
// attribute, and it is escaped by \Drupal\Core\Template\Attribute.
|
||||
$attributes['href'] = $url_string;
|
||||
|
||||
$result = SafeMarkup::format('<a@attributes>@text</a>', array('@attributes' => new Attribute($attributes), '@text' => $variables['text']));
|
||||
|
||||
return $collect_cacheability_metadata ? $generated_link->setGeneratedLink($result) : $result;
|
||||
}
|
||||
|
||||
}
|
98
core/lib/Drupal/Core/Utility/LinkGeneratorInterface.php
Normal file
98
core/lib/Drupal/Core/Utility/LinkGeneratorInterface.php
Normal file
|
@ -0,0 +1,98 @@
|
|||
<?php
|
||||
|
||||
/**
|
||||
* @file
|
||||
* Contains \Drupal\Core\Utility\LinkGeneratorInterface.
|
||||
*/
|
||||
|
||||
namespace Drupal\Core\Utility;
|
||||
|
||||
use Drupal\Core\Link;
|
||||
use Drupal\Core\Url;
|
||||
|
||||
/**
|
||||
* Defines an interface for generating links from route names and parameters.
|
||||
*/
|
||||
interface LinkGeneratorInterface {
|
||||
|
||||
/**
|
||||
* Renders a link to a URL.
|
||||
*
|
||||
* Examples:
|
||||
* @code
|
||||
* $link_generator = \Drupal::service('link_generator');
|
||||
* $installer_url = \Drupal\Core\Url::fromUri('base://core/install.php');
|
||||
* $installer_link = $link_generator->generate($text, $installer_url);
|
||||
* $external_url = \Drupal\Core\Url::fromUri('http://example.com', ['query' => ['foo' => 'bar']]);
|
||||
* $external_link = $link_generator->generate($text, $external_url);
|
||||
* $internal_url = \Drupal\Core\Url::fromRoute('system.admin');
|
||||
* $internal_link = $link_generator->generate($text, $internal_url);
|
||||
* @endcode
|
||||
* However, for links enclosed in translatable text you should use t() and
|
||||
* embed the HTML anchor tag directly in the translated string. For example:
|
||||
* @code
|
||||
* $text = t('Visit the <a href="@url">content types</a> page', array('@url' => \Drupal::url('entity.node_type.collection')));
|
||||
* @endcode
|
||||
* This keeps the context of the link title ('settings' in the example) for
|
||||
* translators.
|
||||
*
|
||||
* @param string|array $text
|
||||
* The link text for the anchor tag as a translated string or render array.
|
||||
* Strings will be sanitized automatically. If you need to output HTML in
|
||||
* the link text, use a render array or an already sanitized string such as
|
||||
* the output of \Drupal\Component\Utility\Xss::filter() or
|
||||
* \Drupal\Component\Utility\SafeMarkup::format().
|
||||
* @param \Drupal\Core\Url $url
|
||||
* The URL object used for the link. Amongst its options, the following may
|
||||
* be set to affect the generated link:
|
||||
* - attributes: An associative array of HTML attributes to apply to the
|
||||
* anchor tag. If element 'class' is included, it must be an array; 'title'
|
||||
* must be a string; other elements are more flexible, as they just need
|
||||
* to work as an argument for the constructor of the class
|
||||
* Drupal\Core\Template\Attribute($options['attributes']).
|
||||
* - language: An optional language object. If the path being linked to is
|
||||
* internal to the site, $options['language'] is used to determine whether
|
||||
* the link is "active", or pointing to the current page (the language as
|
||||
* well as the path must match).
|
||||
* - 'set_active_class': Whether this method should compare the $route_name,
|
||||
* $parameters, language and query options to the current URL to determine
|
||||
* whether the link is "active". Defaults to FALSE. If TRUE, an "active"
|
||||
* class will be applied to the link. It is important to use this
|
||||
* sparingly since it is usually unnecessary and requires extra
|
||||
* processing.
|
||||
* @param bool $collect_cacheability_metadata
|
||||
* (optional) Defaults to FALSE. When TRUE, both the generated link and its
|
||||
* associated cacheability metadata are returned.
|
||||
*
|
||||
* @return string|\Drupal\Core\GeneratedLink
|
||||
* An HTML string containing a link to the given route and parameters.
|
||||
* When $collect_cacheability_metadata is TRUE, a GeneratedLink object is
|
||||
* returned, containing the generated link plus cacheability metadata.
|
||||
*
|
||||
* @throws \Symfony\Component\Routing\Exception\RouteNotFoundException
|
||||
* Thrown when the named route doesn't exist.
|
||||
* @throws \Symfony\Component\Routing\Exception\MissingMandatoryParametersException
|
||||
* Thrown when some parameters are missing that are mandatory for the route.
|
||||
* @throws \Symfony\Component\Routing\Exception\InvalidParameterException
|
||||
* Thrown when a parameter value for a placeholder is not correct because it
|
||||
* does not match the requirement.
|
||||
*/
|
||||
public function generate($text, Url $url, $collect_cacheability_metadata = FALSE);
|
||||
|
||||
/**
|
||||
* Renders a link from a link object.
|
||||
*
|
||||
* @param \Drupal\Core\Link $link
|
||||
* A link object to convert to a string.
|
||||
* @param bool $collect_cacheability_metadata
|
||||
* (optional) Defaults to FALSE. When TRUE, both the generated link and its
|
||||
* associated cacheability metadata are returned.
|
||||
*
|
||||
* @return string|\Drupal\Core\GeneratedLink
|
||||
* An HTML string containing a link to the given route and parameters.
|
||||
* When $collect_cacheability_metadata is TRUE, a GeneratedLink object is
|
||||
* returned, containing the generated link plus cacheability metadata.
|
||||
*/
|
||||
public function generateFromLink(Link $link, $collect_cacheability_metadata = FALSE);
|
||||
|
||||
}
|
234
core/lib/Drupal/Core/Utility/ProjectInfo.php
Normal file
234
core/lib/Drupal/Core/Utility/ProjectInfo.php
Normal file
|
@ -0,0 +1,234 @@
|
|||
<?php
|
||||
|
||||
/**
|
||||
* @file
|
||||
* Contains \Drupal\Core\Utility\ProjectInfo.
|
||||
*
|
||||
* API for building lists of installed projects.
|
||||
*/
|
||||
|
||||
namespace Drupal\Core\Utility;
|
||||
|
||||
use Drupal\Core\Extension\Extension;
|
||||
|
||||
/**
|
||||
* Performs operations on drupal.org project data.
|
||||
*/
|
||||
class ProjectInfo {
|
||||
|
||||
/**
|
||||
* Populates an array of project data.
|
||||
*
|
||||
* @todo https://www.drupal.org/node/2338167 update class since extensions can
|
||||
* no longer be hidden, enabled or disabled. Additionally, base themes have
|
||||
* to be installed for sub themes to work.
|
||||
*
|
||||
* This iterates over a list of the installed modules or themes and groups
|
||||
* them by project and status. A few parts of this function assume that
|
||||
* enabled modules and themes are always processed first, and if disabled
|
||||
* modules or themes are being processed (there is a setting to control if
|
||||
* disabled code should be included in the Available updates report or not),
|
||||
* those are only processed after $projects has been populated with
|
||||
* information about the enabled code. 'Hidden' modules are always ignored.
|
||||
* 'Hidden' themes are ignored only if they have no enabled sub-themes.
|
||||
* This function also records the latest change time on the .info.yml
|
||||
* files for each module or theme, which is important data which is used when
|
||||
* deciding if the available update data should be invalidated.
|
||||
*
|
||||
* @param array $projects
|
||||
* Reference to the array of project data of what's installed on this site.
|
||||
* @param \Drupal\Core\Extension\Extension[] $list
|
||||
* Array of data to process to add the relevant info to the $projects array.
|
||||
* @param string $project_type
|
||||
* The kind of data in the list. Can be 'module' or 'theme'.
|
||||
* @param bool $status
|
||||
* Boolean that controls what status (enabled or disabled) to process out of
|
||||
* the $list and add to the $projects array.
|
||||
* @param array $additional_whitelist
|
||||
* (optional) Array of additional elements to be collected from the .info.yml
|
||||
* file. Defaults to array().
|
||||
*/
|
||||
function processInfoList(array &$projects, array $list, $project_type, $status, array $additional_whitelist = array()) {
|
||||
foreach ($list as $file) {
|
||||
// A disabled or hidden base theme of an enabled sub-theme still has all
|
||||
// of its code run by the sub-theme, so we include it in our "enabled"
|
||||
// projects list.
|
||||
if ($status && !empty($file->sub_themes)) {
|
||||
foreach ($file->sub_themes as $key => $name) {
|
||||
// Build a list of installed sub-themes.
|
||||
if ($list[$key]->status) {
|
||||
$file->installed_sub_themes[$key] = $name;
|
||||
}
|
||||
}
|
||||
// If the theme is uninstalled and there are no installed subthemes, we
|
||||
// should ignore this base theme for the installed case. If the site is
|
||||
// trying to display uninstalled themes, we'll catch it then.
|
||||
if (!$file->status && empty($file->installed_sub_themes)) {
|
||||
continue;
|
||||
}
|
||||
}
|
||||
// Otherwise, just add projects of the proper status to our list.
|
||||
elseif ($file->status != $status) {
|
||||
continue;
|
||||
}
|
||||
|
||||
// Skip if the .info.yml file is broken.
|
||||
if (empty($file->info)) {
|
||||
continue;
|
||||
}
|
||||
|
||||
// Skip if it's a hidden module or hidden theme without installed
|
||||
// sub-themes.
|
||||
if (!empty($file->info['hidden']) && empty($file->installed_sub_themes)) {
|
||||
continue;
|
||||
}
|
||||
|
||||
// If the .info.yml doesn't define the 'project', try to figure it out.
|
||||
if (!isset($file->info['project'])) {
|
||||
$file->info['project'] = $this->getProjectName($file);
|
||||
}
|
||||
|
||||
// If we still don't know the 'project', give up.
|
||||
if (empty($file->info['project'])) {
|
||||
continue;
|
||||
}
|
||||
|
||||
// If we don't already know it, grab the change time on the .info.yml file
|
||||
// itself. Note: we need to use the ctime, not the mtime (modification
|
||||
// time) since many (all?) tar implementations will go out of their way to
|
||||
// set the mtime on the files it creates to the timestamps recorded in the
|
||||
// tarball. We want to see the last time the file was changed on disk,
|
||||
// which is left alone by tar and correctly set to the time the .info.yml
|
||||
// file was unpacked.
|
||||
if (!isset($file->info['_info_file_ctime'])) {
|
||||
$file->info['_info_file_ctime'] = $file->getCTime();
|
||||
}
|
||||
|
||||
if (!isset($file->info['datestamp'])) {
|
||||
$file->info['datestamp'] = 0;
|
||||
}
|
||||
|
||||
$project_name = $file->info['project'];
|
||||
|
||||
// Figure out what project type we're going to use to display this module
|
||||
// or theme. If the project name is 'drupal', we don't want it to show up
|
||||
// under the usual "Modules" section, we put it at a special "Drupal Core"
|
||||
// section at the top of the report.
|
||||
if ($project_name == 'drupal') {
|
||||
$project_display_type = 'core';
|
||||
}
|
||||
else {
|
||||
$project_display_type = $project_type;
|
||||
}
|
||||
if (empty($status) && empty($file->installed_sub_themes)) {
|
||||
// If we're processing disabled modules or themes, append a suffix.
|
||||
// However, we don't do this to a base theme with installed
|
||||
// subthemes, since we treat that case as if it is installed.
|
||||
$project_display_type .= '-disabled';
|
||||
}
|
||||
// Add a list of sub-themes that "depend on" the project and a list of base
|
||||
// themes that are "required by" the project.
|
||||
if ($project_name == 'drupal') {
|
||||
// Drupal core is always required, so this extra info would be noise.
|
||||
$sub_themes = array();
|
||||
$base_themes = array();
|
||||
}
|
||||
else {
|
||||
// Add list of installed sub-themes.
|
||||
$sub_themes = !empty($file->installed_sub_themes) ? $file->installed_sub_themes : array();
|
||||
// Add list of base themes.
|
||||
$base_themes = !empty($file->base_themes) ? $file->base_themes : array();
|
||||
}
|
||||
if (!isset($projects[$project_name])) {
|
||||
// Only process this if we haven't done this project, since a single
|
||||
// project can have multiple modules or themes.
|
||||
$projects[$project_name] = array(
|
||||
'name' => $project_name,
|
||||
// Only save attributes from the .info.yml file we care about so we do
|
||||
// not bloat our RAM usage needlessly.
|
||||
'info' => $this->filterProjectInfo($file->info, $additional_whitelist),
|
||||
'datestamp' => $file->info['datestamp'],
|
||||
'includes' => array($file->getName() => $file->info['name']),
|
||||
'project_type' => $project_display_type,
|
||||
'project_status' => $status,
|
||||
'sub_themes' => $sub_themes,
|
||||
'base_themes' => $base_themes,
|
||||
);
|
||||
}
|
||||
elseif ($projects[$project_name]['project_type'] == $project_display_type) {
|
||||
// Only add the file we're processing to the 'includes' array for this
|
||||
// project if it is of the same type and status (which is encoded in the
|
||||
// $project_display_type). This prevents listing all the disabled
|
||||
// modules included with an enabled project if we happen to be checking
|
||||
// for disabled modules, too.
|
||||
$projects[$project_name]['includes'][$file->getName()] = $file->info['name'];
|
||||
$projects[$project_name]['info']['_info_file_ctime'] = max($projects[$project_name]['info']['_info_file_ctime'], $file->info['_info_file_ctime']);
|
||||
$projects[$project_name]['datestamp'] = max($projects[$project_name]['datestamp'], $file->info['datestamp']);
|
||||
if (!empty($sub_themes)) {
|
||||
$projects[$project_name]['sub_themes'] += $sub_themes;
|
||||
}
|
||||
if (!empty($base_themes)) {
|
||||
$projects[$project_name]['base_themes'] += $base_themes;
|
||||
}
|
||||
}
|
||||
elseif (empty($status)) {
|
||||
// If we have a project_name that matches, but the project_display_type
|
||||
// does not, it means we're processing a disabled module or theme that
|
||||
// belongs to a project that has some enabled code. In this case, we add
|
||||
// the disabled thing into a separate array for separate display.
|
||||
$projects[$project_name]['disabled'][$file->getName()] = $file->info['name'];
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Determines what project a given file object belongs to.
|
||||
*
|
||||
* @param \Drupal\Core\Extension\Extension $file
|
||||
* An extension object.
|
||||
*
|
||||
* @return string
|
||||
* The canonical project short name.
|
||||
*/
|
||||
function getProjectName(Extension $file) {
|
||||
$project_name = '';
|
||||
if (isset($file->info['project'])) {
|
||||
$project_name = $file->info['project'];
|
||||
}
|
||||
elseif (strpos($file->getPath(), 'core/modules') === 0) {
|
||||
$project_name = 'drupal';
|
||||
}
|
||||
return $project_name;
|
||||
}
|
||||
|
||||
/**
|
||||
* Filters the project .info.yml data to only save attributes we need.
|
||||
*
|
||||
* @param array $info
|
||||
* Array of .info.yml file data as returned by
|
||||
* \Drupal\Core\Extension\InfoParser.
|
||||
* @param $additional_whitelist
|
||||
* (optional) Array of additional elements to be collected from the .info.yml
|
||||
* file. Defaults to array().
|
||||
*
|
||||
* @return
|
||||
* Array of .info.yml file data we need for the update manager.
|
||||
*
|
||||
* @see \Drupal\Core\Utility\ProjectInfo->processInfoList()
|
||||
*/
|
||||
function filterProjectInfo($info, $additional_whitelist = array()) {
|
||||
$whitelist = array(
|
||||
'_info_file_ctime',
|
||||
'datestamp',
|
||||
'major',
|
||||
'name',
|
||||
'package',
|
||||
'project',
|
||||
'project status url',
|
||||
'version',
|
||||
);
|
||||
$whitelist = array_merge($whitelist, $additional_whitelist);
|
||||
return array_intersect_key($info, array_combine($whitelist, $whitelist));
|
||||
}
|
||||
|
||||
}
|
172
core/lib/Drupal/Core/Utility/ThemeRegistry.php
Normal file
172
core/lib/Drupal/Core/Utility/ThemeRegistry.php
Normal file
|
@ -0,0 +1,172 @@
|
|||
<?php
|
||||
|
||||
/**
|
||||
* @file
|
||||
* Contains \Drupal\Core\Utility\ThemeRegistry.
|
||||
*/
|
||||
|
||||
namespace Drupal\Core\Utility;
|
||||
|
||||
use Drupal\Core\Cache\Cache;
|
||||
use Drupal\Core\Cache\CacheBackendInterface;
|
||||
use Drupal\Core\Cache\CacheCollector;
|
||||
use Drupal\Core\DestructableInterface;
|
||||
use Drupal\Core\Lock\LockBackendInterface;
|
||||
|
||||
/**
|
||||
* Builds the run-time theme registry.
|
||||
*
|
||||
* A cache collector to allow the theme registry to be accessed as a
|
||||
* complete registry, while internally caching only the parts of the registry
|
||||
* that are actually in use on the site. On cache misses the complete
|
||||
* theme registry is loaded and used to update the run-time cache.
|
||||
*/
|
||||
class ThemeRegistry extends CacheCollector implements DestructableInterface {
|
||||
|
||||
/**
|
||||
* Whether the partial registry can be persisted to the cache.
|
||||
*
|
||||
* This is only allowed if all modules and the request method is GET. _theme()
|
||||
* should be very rarely called on POST requests and this avoids polluting
|
||||
* the runtime cache.
|
||||
*/
|
||||
protected $persistable;
|
||||
|
||||
/**
|
||||
* The complete theme registry array.
|
||||
*/
|
||||
protected $completeRegistry;
|
||||
|
||||
/**
|
||||
* Constructs a ThemeRegistry object.
|
||||
*
|
||||
* @param string $cid
|
||||
* The cid for the array being cached.
|
||||
* @param \Drupal\Core\Cache\CacheBackendInterface $cache
|
||||
* The cache backend.
|
||||
* @param \Drupal\Core\Lock\LockBackendInterface $lock
|
||||
* The lock backend.
|
||||
* @param array $tags
|
||||
* (optional) The tags to specify for the cache item.
|
||||
* @param bool $modules_loaded
|
||||
* Whether all modules have already been loaded.
|
||||
*/
|
||||
function __construct($cid, CacheBackendInterface $cache, LockBackendInterface $lock, $tags = array(), $modules_loaded = FALSE) {
|
||||
$this->cid = $cid;
|
||||
$this->cache = $cache;
|
||||
$this->lock = $lock;
|
||||
$this->tags = $tags;
|
||||
$this->persistable = $modules_loaded && \Drupal::hasRequest() && \Drupal::request()->isMethod('GET');
|
||||
|
||||
// @todo: Implement lazyload.
|
||||
$this->cacheLoaded = TRUE;
|
||||
|
||||
if ($this->persistable && $cached = $this->cache->get($this->cid)) {
|
||||
$this->storage = $cached->data;
|
||||
}
|
||||
else {
|
||||
// If there is no runtime cache stored, fetch the full theme registry,
|
||||
// but then initialize each value to NULL. This allows offsetExists()
|
||||
// to function correctly on non-registered theme hooks without triggering
|
||||
// a call to resolveCacheMiss().
|
||||
$this->storage = $this->initializeRegistry();
|
||||
foreach (array_keys($this->storage) as $key) {
|
||||
$this->persist($key);
|
||||
}
|
||||
// RegistryTest::testRaceCondition() ensures that the cache entry is
|
||||
// written on the initial construction of the theme registry.
|
||||
$this->updateCache();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Initializes the full theme registry.
|
||||
*
|
||||
* @return
|
||||
* An array with the keys of the full theme registry, but the values
|
||||
* initialized to NULL.
|
||||
*/
|
||||
function initializeRegistry() {
|
||||
// @todo DIC this.
|
||||
$this->completeRegistry = \Drupal::service('theme.registry')->get();
|
||||
|
||||
return array_fill_keys(array_keys($this->completeRegistry), NULL);
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function has($key) {
|
||||
// Since the theme registry allows for theme hooks to be requested that
|
||||
// are not registered, just check the existence of the key in the registry.
|
||||
// Use array_key_exists() here since a NULL value indicates that the theme
|
||||
// hook exists but has not yet been requested.
|
||||
return array_key_exists($key, $this->storage);
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function get($key) {
|
||||
// If the offset is set but empty, it is a registered theme hook that has
|
||||
// not yet been requested. Offsets that do not exist at all were not
|
||||
// registered in hook_theme().
|
||||
if (isset($this->storage[$key])) {
|
||||
return $this->storage[$key];
|
||||
}
|
||||
elseif (array_key_exists($key, $this->storage)) {
|
||||
return $this->resolveCacheMiss($key);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function resolveCacheMiss($key) {
|
||||
if (!isset($this->completeRegistry)) {
|
||||
$this->completeRegistry = \Drupal::service('theme.registry')->get();
|
||||
}
|
||||
$this->storage[$key] = $this->completeRegistry[$key];
|
||||
if ($this->persistable) {
|
||||
$this->persist($key);
|
||||
}
|
||||
return $this->storage[$key];
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
protected function updateCache($lock = TRUE) {
|
||||
if (!$this->persistable) {
|
||||
return;
|
||||
}
|
||||
// @todo: Is the custom implementation necessary?
|
||||
$data = array();
|
||||
foreach ($this->keysToPersist as $offset => $persist) {
|
||||
if ($persist) {
|
||||
$data[$offset] = $this->storage[$offset];
|
||||
}
|
||||
}
|
||||
if (empty($data)) {
|
||||
return;
|
||||
}
|
||||
|
||||
$lock_name = $this->cid . ':' . __CLASS__;
|
||||
if (!$lock || $this->lock->acquire($lock_name)) {
|
||||
if ($cached = $this->cache->get($this->cid)) {
|
||||
// Use array merge instead of union so that filled in values in $data
|
||||
// overwrite empty values in the current cache.
|
||||
$data = array_merge($cached->data, $data);
|
||||
}
|
||||
else {
|
||||
$registry = $this->initializeRegistry();
|
||||
$data = array_merge($registry, $data);
|
||||
}
|
||||
$this->cache->set($this->cid, $data, Cache::PERMANENT, $this->tags);
|
||||
if ($lock) {
|
||||
$this->lock->release($lock_name);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
}
|
363
core/lib/Drupal/Core/Utility/Token.php
Normal file
363
core/lib/Drupal/Core/Utility/Token.php
Normal file
|
@ -0,0 +1,363 @@
|
|||
<?php
|
||||
|
||||
/**
|
||||
* @file
|
||||
* Contains \Drupal\Core\Utility\Token.
|
||||
*/
|
||||
|
||||
namespace Drupal\Core\Utility;
|
||||
|
||||
use Drupal\Core\Cache\Cache;
|
||||
use Drupal\Core\Cache\CacheBackendInterface;
|
||||
use Drupal\Core\Cache\CacheTagsInvalidatorInterface;
|
||||
use Drupal\Core\Extension\ModuleHandlerInterface;
|
||||
use Drupal\Core\Language\LanguageInterface;
|
||||
use Drupal\Core\Language\LanguageManagerInterface;
|
||||
|
||||
/**
|
||||
* Drupal placeholder/token replacement system.
|
||||
*
|
||||
* API functions for replacing placeholders in text with meaningful values.
|
||||
*
|
||||
* For example: When configuring automated emails, an administrator enters
|
||||
* standard text for the email. Variables like the title of a node and the date
|
||||
* the email was sent can be entered as placeholders like [node:title] and
|
||||
* [date:short]. When a Drupal module prepares to send the email, it can call
|
||||
* the Token::replace() function, passing in the text. The token system will
|
||||
* scan the text for placeholder tokens, give other modules an opportunity to
|
||||
* replace them with meaningful text, then return the final product to the
|
||||
* original module.
|
||||
*
|
||||
* Tokens follow the form: [$type:$name], where $type is a general class of
|
||||
* tokens like 'node', 'user', or 'comment' and $name is the name of a given
|
||||
* placeholder. For example, [node:title] or [node:created:since].
|
||||
*
|
||||
* In addition to raw text containing placeholders, modules may pass in an array
|
||||
* of objects to be used when performing the replacement. The objects should be
|
||||
* keyed by the token type they correspond to. For example:
|
||||
*
|
||||
* @code
|
||||
* // Load a node and a user, then replace tokens in the text.
|
||||
* $text = 'On [date:short], [user:name] read [node:title].';
|
||||
* $node = Node::load(1);
|
||||
* $user = User::load(1);
|
||||
*
|
||||
* // [date:...] tokens use the current date automatically.
|
||||
* $data = array('node' => $node, 'user' => $user);
|
||||
* return Token::replace($text, $data);
|
||||
* @endcode
|
||||
*
|
||||
* Some tokens may be chained in the form of [$type:$pointer:$name], where $type
|
||||
* is a normal token type, $pointer is a reference to another token type, and
|
||||
* $name is the name of a given placeholder. For example, [node:author:mail]. In
|
||||
* that example, 'author' is a pointer to the 'user' account that created the
|
||||
* node, and 'mail' is a placeholder available for any 'user'.
|
||||
*
|
||||
* @see Token::replace()
|
||||
* @see hook_tokens()
|
||||
* @see hook_token_info()
|
||||
*/
|
||||
class Token {
|
||||
|
||||
/**
|
||||
* The tag to cache token info with.
|
||||
*/
|
||||
const TOKEN_INFO_CACHE_TAG = 'token_info';
|
||||
|
||||
/**
|
||||
* The token cache.
|
||||
*
|
||||
* @var \Drupal\Core\Cache\CacheBackendInterface
|
||||
*/
|
||||
protected $cache;
|
||||
|
||||
/**
|
||||
* The language manager.
|
||||
*
|
||||
* @var \Drupal\Core\Language\LanguageManagerInterface
|
||||
*/
|
||||
protected $languageManager;
|
||||
|
||||
/**
|
||||
* Token definitions.
|
||||
*
|
||||
* @var array[]|null
|
||||
* An array of token definitions, or NULL when the definitions are not set.
|
||||
*
|
||||
* @see self::setInfo()
|
||||
* @see self::getInfo()
|
||||
* @see self::resetInfo()
|
||||
*/
|
||||
protected $tokenInfo;
|
||||
|
||||
/**
|
||||
* The module handler service.
|
||||
*
|
||||
* @var \Drupal\Core\Extension\ModuleHandlerInterface
|
||||
*/
|
||||
protected $moduleHandler;
|
||||
|
||||
/**
|
||||
* The cache tags invalidator.
|
||||
*
|
||||
* @var \Drupal\Core\Cache\CacheTagsInvalidatorInterface
|
||||
*/
|
||||
protected $cacheTagsInvalidator;
|
||||
|
||||
/**
|
||||
* Constructs a new class instance.
|
||||
*
|
||||
* @param \Drupal\Core\Extension\ModuleHandlerInterface $module_handler
|
||||
* The module handler.
|
||||
* @param \Drupal\Core\Cache\CacheBackendInterface $cache
|
||||
* The token cache.
|
||||
* @param \Drupal\Core\Language\LanguageManagerInterface $language_manager
|
||||
* The language manager.
|
||||
* @param \Drupal\Core\Cache\CacheTagsInvalidatorInterface $cache_tags_invalidator
|
||||
* The cache tags invalidator.
|
||||
*/
|
||||
public function __construct(ModuleHandlerInterface $module_handler, CacheBackendInterface $cache, LanguageManagerInterface $language_manager, CacheTagsInvalidatorInterface $cache_tags_invalidator) {
|
||||
$this->cache = $cache;
|
||||
$this->languageManager = $language_manager;
|
||||
$this->moduleHandler = $module_handler;
|
||||
$this->cacheTagsInvalidator = $cache_tags_invalidator;
|
||||
}
|
||||
|
||||
/**
|
||||
* Replaces all tokens in a given string with appropriate values.
|
||||
*
|
||||
* @param string $text
|
||||
* A string potentially containing replaceable tokens.
|
||||
* @param array $data
|
||||
* (optional) An array of keyed objects. For simple replacement scenarios
|
||||
* 'node', 'user', and others are common keys, with an accompanying node or
|
||||
* user object being the value. Some token types, like 'site', do not require
|
||||
* any explicit information from $data and can be replaced even if it is
|
||||
* empty.
|
||||
* @param array $options
|
||||
* (optional) A keyed array of settings and flags to control the token
|
||||
* replacement process. Supported options are:
|
||||
* - langcode: A language code to be used when generating locale-sensitive
|
||||
* tokens.
|
||||
* - callback: A callback function that will be used to post-process the
|
||||
* array of token replacements after they are generated. For example, a
|
||||
* module using tokens in a text-only email might provide a callback to
|
||||
* strip HTML entities from token values before they are inserted into the
|
||||
* final text.
|
||||
* - clear: A boolean flag indicating that tokens should be removed from the
|
||||
* final text if no replacement value can be generated.
|
||||
* - sanitize: A boolean flag indicating that tokens should be sanitized for
|
||||
* display to a web browser. Defaults to TRUE. Developers who set this
|
||||
* option to FALSE assume responsibility for running
|
||||
* \Drupal\Component\Utility\Xss::filter(),
|
||||
* \Drupal\Component\Utility\SafeMarkup::checkPlain() or other appropriate
|
||||
* scrubbing functions before displaying data to users.
|
||||
*
|
||||
* @return string
|
||||
* Text with tokens replaced.
|
||||
*/
|
||||
public function replace($text, array $data = array(), array $options = array()) {
|
||||
$text_tokens = $this->scan($text);
|
||||
if (empty($text_tokens)) {
|
||||
return $text;
|
||||
}
|
||||
|
||||
$replacements = array();
|
||||
foreach ($text_tokens as $type => $tokens) {
|
||||
$replacements += $this->generate($type, $tokens, $data, $options);
|
||||
if (!empty($options['clear'])) {
|
||||
$replacements += array_fill_keys($tokens, '');
|
||||
}
|
||||
}
|
||||
|
||||
// Optionally alter the list of replacement values.
|
||||
if (!empty($options['callback'])) {
|
||||
$function = $options['callback'];
|
||||
$function($replacements, $data, $options);
|
||||
}
|
||||
|
||||
$tokens = array_keys($replacements);
|
||||
$values = array_values($replacements);
|
||||
|
||||
return str_replace($tokens, $values, $text);
|
||||
}
|
||||
|
||||
/**
|
||||
* Builds a list of all token-like patterns that appear in the text.
|
||||
*
|
||||
* @param string $text
|
||||
* The text to be scanned for possible tokens.
|
||||
*
|
||||
* @return array
|
||||
* An associative array of discovered tokens, grouped by type.
|
||||
*/
|
||||
public function scan($text) {
|
||||
// Matches tokens with the following pattern: [$type:$name]
|
||||
// $type and $name may not contain [ ] characters.
|
||||
// $type may not contain : or whitespace characters, but $name may.
|
||||
preg_match_all('/
|
||||
\[ # [ - pattern start
|
||||
([^\s\[\]:]+) # match $type not containing whitespace : [ or ]
|
||||
: # : - separator
|
||||
([^\[\]]+) # match $name not containing [ or ]
|
||||
\] # ] - pattern end
|
||||
/x', $text, $matches);
|
||||
|
||||
$types = $matches[1];
|
||||
$tokens = $matches[2];
|
||||
|
||||
// Iterate through the matches, building an associative array containing
|
||||
// $tokens grouped by $types, pointing to the version of the token found in
|
||||
// the source text. For example, $results['node']['title'] = '[node:title]';
|
||||
$results = array();
|
||||
for ($i = 0; $i < count($tokens); $i++) {
|
||||
$results[$types[$i]][$tokens[$i]] = $matches[0][$i];
|
||||
}
|
||||
|
||||
return $results;
|
||||
}
|
||||
|
||||
/**
|
||||
* Generates replacement values for a list of tokens.
|
||||
*
|
||||
* @param string $type
|
||||
* The type of token being replaced. 'node', 'user', and 'date' are common.
|
||||
* @param array $tokens
|
||||
* An array of tokens to be replaced, keyed by the literal text of the token
|
||||
* as it appeared in the source text.
|
||||
* @param array $data
|
||||
* (optional) An array of keyed objects. For simple replacement scenarios
|
||||
* 'node', 'user', and others are common keys, with an accompanying node or
|
||||
* user object being the value. Some token types, like 'site', do not require
|
||||
* any explicit information from $data and can be replaced even if it is
|
||||
* empty.
|
||||
* @param array $options
|
||||
* (optional) A keyed array of settings and flags to control the token
|
||||
* replacement process. Supported options are:
|
||||
* - langcode: A language code to be used when generating locale-sensitive
|
||||
* tokens.
|
||||
* - callback: A callback function that will be used to post-process the
|
||||
* array of token replacements after they are generated. Can be used when
|
||||
* modules require special formatting of token text, for example URL
|
||||
* encoding or truncation to a specific length.
|
||||
* - sanitize: A boolean flag indicating that tokens should be sanitized for
|
||||
* display to a web browser. Developers who set this option to FALSE assume
|
||||
* responsibility for running \Drupal\Component\Utility\Xss::filter(),
|
||||
* \Drupal\Component\Utility\SafeMarkup::checkPlain() or other appropriate
|
||||
* scrubbing functions before displaying data to users.
|
||||
*
|
||||
* @return array
|
||||
* An associative array of replacement values, keyed by the original 'raw'
|
||||
* tokens that were found in the source text. For example:
|
||||
* $results['[node:title]'] = 'My new node';
|
||||
*
|
||||
* @see hook_tokens()
|
||||
* @see hook_tokens_alter()
|
||||
*/
|
||||
public function generate($type, array $tokens, array $data = array(), array $options = array()) {
|
||||
$options += array('sanitize' => TRUE);
|
||||
$replacements = $this->moduleHandler->invokeAll('tokens', array($type, $tokens, $data, $options));
|
||||
|
||||
// Allow other modules to alter the replacements.
|
||||
$context = array(
|
||||
'type' => $type,
|
||||
'tokens' => $tokens,
|
||||
'data' => $data,
|
||||
'options' => $options,
|
||||
);
|
||||
$this->moduleHandler->alter('tokens', $replacements, $context);
|
||||
|
||||
return $replacements;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns a list of tokens that begin with a specific prefix.
|
||||
*
|
||||
* Used to extract a group of 'chained' tokens (such as [node:author:name])
|
||||
* from the full list of tokens found in text. For example:
|
||||
* @code
|
||||
* $data = array(
|
||||
* 'author:name' => '[node:author:name]',
|
||||
* 'title' => '[node:title]',
|
||||
* 'created' => '[node:created]',
|
||||
* );
|
||||
* $results = Token::findWithPrefix($data, 'author');
|
||||
* $results == array('name' => '[node:author:name]');
|
||||
* @endcode
|
||||
*
|
||||
* @param array $tokens
|
||||
* A keyed array of tokens, and their original raw form in the source text.
|
||||
* @param string $prefix
|
||||
* A textual string to be matched at the beginning of the token.
|
||||
* @param string $delimiter
|
||||
* (optional) A string containing the character that separates the prefix from
|
||||
* the rest of the token. Defaults to ':'.
|
||||
*
|
||||
* @return array
|
||||
* An associative array of discovered tokens, with the prefix and delimiter
|
||||
* stripped from the key.
|
||||
*/
|
||||
public function findWithPrefix(array $tokens, $prefix, $delimiter = ':') {
|
||||
$results = array();
|
||||
foreach ($tokens as $token => $raw) {
|
||||
$parts = explode($delimiter, $token, 2);
|
||||
if (count($parts) == 2 && $parts[0] == $prefix) {
|
||||
$results[$parts[1]] = $raw;
|
||||
}
|
||||
}
|
||||
return $results;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns metadata describing supported tokens.
|
||||
*
|
||||
* The metadata array contains token type, name, and description data as well
|
||||
* as an optional pointer indicating that the token chains to another set of
|
||||
* tokens.
|
||||
*
|
||||
* @return array
|
||||
* An associative array of token information, grouped by token type. The
|
||||
* array structure is identical to that of hook_token_info().
|
||||
*
|
||||
* @see hook_token_info()
|
||||
*/
|
||||
public function getInfo() {
|
||||
if (is_null($this->tokenInfo)) {
|
||||
$cache_id = 'token_info:' . $this->languageManager->getCurrentLanguage(LanguageInterface::TYPE_CONTENT)->getId();
|
||||
$cache = $this->cache->get($cache_id);
|
||||
if ($cache) {
|
||||
$this->tokenInfo = $cache->data;
|
||||
}
|
||||
else {
|
||||
$this->tokenInfo = $this->moduleHandler->invokeAll('token_info');
|
||||
$this->moduleHandler->alter('token_info', $this->tokenInfo);
|
||||
$this->cache->set($cache_id, $this->tokenInfo, CacheBackendInterface::CACHE_PERMANENT, array(
|
||||
static::TOKEN_INFO_CACHE_TAG,
|
||||
));
|
||||
}
|
||||
}
|
||||
|
||||
return $this->tokenInfo;
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets metadata describing supported tokens.
|
||||
*
|
||||
* @param array $tokens
|
||||
* Token metadata that has an identical structure to the return value of
|
||||
* hook_token_info().
|
||||
*
|
||||
* @see hook_token_info()
|
||||
*/
|
||||
public function setInfo(array $tokens) {
|
||||
$this->tokenInfo = $tokens;
|
||||
}
|
||||
|
||||
/**
|
||||
* Resets metadata describing supported tokens.
|
||||
*/
|
||||
public function resetInfo() {
|
||||
$this->tokenInfo = NULL;
|
||||
$this->cacheTagsInvalidator->invalidateTags([static::TOKEN_INFO_CACHE_TAG]);
|
||||
}
|
||||
}
|
205
core/lib/Drupal/Core/Utility/UnroutedUrlAssembler.php
Normal file
205
core/lib/Drupal/Core/Utility/UnroutedUrlAssembler.php
Normal file
|
@ -0,0 +1,205 @@
|
|||
<?php
|
||||
|
||||
/**
|
||||
* @file
|
||||
* Contains \Drupal\Core\Utility\UnroutedUrlAssembler.
|
||||
*/
|
||||
|
||||
namespace Drupal\Core\Utility;
|
||||
|
||||
use Drupal\Component\Utility\SafeMarkup;
|
||||
use Drupal\Component\Utility\UrlHelper;
|
||||
use Drupal\Core\Config\ConfigFactoryInterface;
|
||||
use Drupal\Core\GeneratedUrl;
|
||||
use Drupal\Core\PathProcessor\OutboundPathProcessorInterface;
|
||||
use Symfony\Component\HttpFoundation\RequestStack;
|
||||
|
||||
/**
|
||||
* Provides a way to build external or non Drupal local domain URLs.
|
||||
*
|
||||
* It takes into account configured safe HTTP protocols.
|
||||
*/
|
||||
class UnroutedUrlAssembler implements UnroutedUrlAssemblerInterface {
|
||||
|
||||
/**
|
||||
* A request stack object.
|
||||
*
|
||||
* @var \Symfony\Component\HttpFoundation\RequestStack
|
||||
*/
|
||||
protected $requestStack;
|
||||
|
||||
/**
|
||||
* The outbound path processor.
|
||||
*
|
||||
* @var \Drupal\Core\PathProcessor\OutboundPathProcessorInterface
|
||||
*/
|
||||
protected $pathProcessor;
|
||||
|
||||
/**
|
||||
* Constructs a new unroutedUrlAssembler object.
|
||||
*
|
||||
* @param \Symfony\Component\HttpFoundation\RequestStack $request_stack
|
||||
* A request stack object.
|
||||
* @param \Drupal\Core\Config\ConfigFactoryInterface $config
|
||||
* The config factory.
|
||||
* @param \Drupal\Core\PathProcessor\OutboundPathProcessorInterface $path_processor
|
||||
* The output path processor.
|
||||
*/
|
||||
public function __construct(RequestStack $request_stack, ConfigFactoryInterface $config, OutboundPathProcessorInterface $path_processor) {
|
||||
$allowed_protocols = $config->get('system.filter')->get('protocols') ?: ['http', 'https'];
|
||||
UrlHelper::setAllowedProtocols($allowed_protocols);
|
||||
$this->requestStack = $request_stack;
|
||||
$this->pathProcessor = $path_processor;
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*
|
||||
* This is a helper function that calls buildExternalUrl() or buildLocalUrl()
|
||||
* based on a check of whether the path is a valid external URL.
|
||||
*/
|
||||
public function assemble($uri, array $options = [], $collect_cacheability_metadata = FALSE) {
|
||||
// Note that UrlHelper::isExternal will return FALSE if the $uri has a
|
||||
// disallowed protocol. This is later made safe since we always add at
|
||||
// least a leading slash.
|
||||
if (parse_url($uri, PHP_URL_SCHEME) === 'base') {
|
||||
return $this->buildLocalUrl($uri, $options, $collect_cacheability_metadata);
|
||||
}
|
||||
elseif (UrlHelper::isExternal($uri)) {
|
||||
// UrlHelper::isExternal() only returns true for safe protocols.
|
||||
return $this->buildExternalUrl($uri, $options, $collect_cacheability_metadata);
|
||||
}
|
||||
throw new \InvalidArgumentException(SafeMarkup::format('The URI "@uri" is invalid. You must use a valid URI scheme. Use base: for a path, e.g., to a Drupal file that needs the base path. Do not use this for internal paths controlled by Drupal.', ['@uri' => $uri]));
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
protected function buildExternalUrl($uri, array $options = [], $collect_cacheability_metadata = FALSE) {
|
||||
$this->addOptionDefaults($options);
|
||||
// Split off the fragment.
|
||||
if (strpos($uri, '#') !== FALSE) {
|
||||
list($uri, $old_fragment) = explode('#', $uri, 2);
|
||||
// If $options contains no fragment, take it over from the path.
|
||||
if (isset($old_fragment) && !$options['fragment']) {
|
||||
$options['fragment'] = '#' . $old_fragment;
|
||||
}
|
||||
}
|
||||
|
||||
if (isset($options['https'])) {
|
||||
if ($options['https'] === TRUE) {
|
||||
$uri = str_replace('http://', 'https://', $uri);
|
||||
}
|
||||
elseif ($options['https'] === FALSE) {
|
||||
$uri = str_replace('https://', 'http://', $uri);
|
||||
}
|
||||
}
|
||||
// Append the query.
|
||||
if ($options['query']) {
|
||||
$uri .= (strpos($uri, '?') !== FALSE ? '&' : '?') . UrlHelper::buildQuery($options['query']);
|
||||
}
|
||||
// Reassemble.
|
||||
$url = $uri . $options['fragment'];
|
||||
return $collect_cacheability_metadata ? (new GeneratedUrl())->setGeneratedUrl($url) : $url;
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
protected function buildLocalUrl($uri, array $options = [], $collect_cacheability_metadata = FALSE) {
|
||||
$generated_url = $collect_cacheability_metadata ? new GeneratedUrl() : NULL;
|
||||
|
||||
$this->addOptionDefaults($options);
|
||||
$request = $this->requestStack->getCurrentRequest();
|
||||
|
||||
// Remove the base: scheme.
|
||||
// @todo Consider using a class constant for this in
|
||||
// https://www.drupal.org/node/2417459
|
||||
$uri = substr($uri, 5);
|
||||
|
||||
// Strip leading slashes from internal paths to prevent them becoming
|
||||
// external URLs without protocol. /example.com should not be turned into
|
||||
// //example.com.
|
||||
$uri = ltrim($uri, '/');
|
||||
|
||||
// Allow (outbound) path processing, if needed. A valid use case is the path
|
||||
// alias overview form:
|
||||
// @see \Drupal\path\Controller\PathController::adminOverview().
|
||||
if (!empty($options['path_processing'])) {
|
||||
// Do not pass the request, since this is a special case and we do not
|
||||
// want to include e.g. the request language in the processing.
|
||||
$uri = $this->pathProcessor->processOutbound($uri, $options, NULL, $generated_url);
|
||||
}
|
||||
|
||||
// Add any subdirectory where Drupal is installed.
|
||||
$current_base_path = $request->getBasePath() . '/';
|
||||
|
||||
if ($options['absolute']) {
|
||||
$current_base_url = $request->getSchemeAndHttpHost() . $current_base_path;
|
||||
if (isset($options['https'])) {
|
||||
if (!empty($options['https'])) {
|
||||
$base = str_replace('http://', 'https://', $current_base_url);
|
||||
$options['absolute'] = TRUE;
|
||||
}
|
||||
else {
|
||||
$base = str_replace('https://', 'http://', $current_base_url);
|
||||
$options['absolute'] = TRUE;
|
||||
}
|
||||
}
|
||||
else {
|
||||
$base = $current_base_url;
|
||||
}
|
||||
if ($collect_cacheability_metadata) {
|
||||
$generated_url->addCacheContexts(['url.site']);
|
||||
}
|
||||
}
|
||||
else {
|
||||
$base = $current_base_path;
|
||||
}
|
||||
|
||||
$prefix = empty($uri) ? rtrim($options['prefix'], '/') : $options['prefix'];
|
||||
|
||||
$uri = str_replace('%2F', '/', rawurlencode($prefix . $uri));
|
||||
$query = $options['query'] ? ('?' . UrlHelper::buildQuery($options['query'])) : '';
|
||||
$url = $base . $options['script'] . $uri . $query . $options['fragment'];
|
||||
return $collect_cacheability_metadata ? $generated_url->setGeneratedUrl($url) : $url;
|
||||
}
|
||||
|
||||
/**
|
||||
* Merges in default defaults
|
||||
*
|
||||
* @param array $options
|
||||
* The options to merge in the defaults.
|
||||
*/
|
||||
protected function addOptionDefaults(array &$options) {
|
||||
$request = $this->requestStack->getCurrentRequest();
|
||||
$current_base_path = $request->getBasePath() . '/';
|
||||
$current_script_path = '';
|
||||
$base_path_with_script = $request->getBaseUrl();
|
||||
|
||||
// If the current request was made with the script name (eg, index.php) in
|
||||
// it, then extract it, making sure the leading / is gone, and a trailing /
|
||||
// is added, to allow simple string concatenation with other parts. This
|
||||
// mirrors code from UrlGenerator::generateFromPath().
|
||||
if (!empty($base_path_with_script)) {
|
||||
$script_name = $request->getScriptName();
|
||||
if (strpos($base_path_with_script, $script_name) !== FALSE) {
|
||||
$current_script_path = ltrim(substr($script_name, strlen($current_base_path)), '/') . '/';
|
||||
}
|
||||
}
|
||||
|
||||
// Merge in defaults.
|
||||
$options += [
|
||||
'fragment' => '',
|
||||
'query' => [],
|
||||
'absolute' => FALSE,
|
||||
'prefix' => '',
|
||||
'script' => $current_script_path,
|
||||
];
|
||||
|
||||
if (isset($options['fragment']) && $options['fragment'] !== '') {
|
||||
$options['fragment'] = '#' . $options['fragment'];
|
||||
}
|
||||
}
|
||||
|
||||
}
|
|
@ -0,0 +1,61 @@
|
|||
<?php
|
||||
/**
|
||||
* @file
|
||||
* Contains \Drupal\Core\Utility\UnroutedUrlAssemblerInterface.
|
||||
*/
|
||||
|
||||
namespace Drupal\Core\Utility;
|
||||
|
||||
/**
|
||||
* Provides a way to build external or non Drupal local domain URLs.
|
||||
*/
|
||||
interface UnroutedUrlAssemblerInterface {
|
||||
|
||||
/**
|
||||
* Builds a domain-local or external URL from a URI.
|
||||
*
|
||||
* For actual implementations the logic probably has to be split up between
|
||||
* domain-local URIs and external URLs.
|
||||
*
|
||||
* @param string $uri
|
||||
* A local URI or an external URL being linked to, such as "base:foo"
|
||||
* or "http://example.com/foo".
|
||||
* - If you provide a full URL, it will be considered an external URL as
|
||||
* long as it has an allowed protocol.
|
||||
* - If you provide only a local URI (e.g. "base:foo"), it will be
|
||||
* considered a path local to Drupal, but not handled by the routing
|
||||
* system. The base path (the subdirectory where the front controller
|
||||
* is found) will be added to the path. Additional query arguments for
|
||||
* local paths must be supplied in $options['query'], not part of $uri.
|
||||
* - If your external URL contains a query (e.g. http://example.com/foo?a=b),
|
||||
* then you can either URL encode the query keys and values yourself and
|
||||
* include them in $uri, or use $options['query'] to let this method
|
||||
* URL encode them.
|
||||
* @param array $options
|
||||
* (optional) An associative array of additional options, with the following
|
||||
* elements:
|
||||
* - 'query': An array of query key/value-pairs (without any URL-encoding) to
|
||||
* append to the URL.
|
||||
* - 'fragment': A fragment identifier (named anchor) to append to the URL.
|
||||
* Do not include the leading '#' character.
|
||||
* - 'absolute': Defaults to FALSE. Whether to force the output to be an
|
||||
* absolute link (beginning with http:). Useful for links that will be
|
||||
* displayed outside the site, such as in an RSS feed.
|
||||
* - 'https': Whether this URL should point to a secure location. If not
|
||||
* defined, the current scheme is used, so the user stays on HTTP or HTTPS
|
||||
* respectively. TRUE enforces HTTPS and FALSE enforces HTTP.
|
||||
* @param bool $collect_cacheability_metadata
|
||||
* (optional) Defaults to FALSE. When TRUE, both the generated URL and its
|
||||
* associated cacheability metadata are returned.
|
||||
*
|
||||
* @return string|\Drupal\Core\GeneratedUrl
|
||||
* A string containing a relative or absolute URL.
|
||||
* When $collect_cacheability_metadata is TRUE, a GeneratedUrl object is
|
||||
* returned, containing the generated URL plus cacheability metadata.
|
||||
*
|
||||
* @throws \InvalidArgumentException
|
||||
* Thrown when the passed in path has no scheme.
|
||||
*/
|
||||
public function assemble($uri, array $options = array(), $collect_cacheability_metadata = FALSE);
|
||||
|
||||
}
|
13
core/lib/Drupal/Core/Utility/UpdateException.php
Normal file
13
core/lib/Drupal/Core/Utility/UpdateException.php
Normal file
|
@ -0,0 +1,13 @@
|
|||
<?php
|
||||
|
||||
/**
|
||||
* @file
|
||||
* Contains \Drupal\Core\Utility\UpdateException.
|
||||
*/
|
||||
|
||||
namespace Drupal\Core\Utility;
|
||||
|
||||
/**
|
||||
* Exception class used to throw error if a module update fails.
|
||||
*/
|
||||
class UpdateException extends \Exception { }
|
261
core/lib/Drupal/Core/Utility/token.api.php
Normal file
261
core/lib/Drupal/Core/Utility/token.api.php
Normal file
|
@ -0,0 +1,261 @@
|
|||
<?php
|
||||
|
||||
/**
|
||||
* @file
|
||||
* Hooks related to the Token system.
|
||||
*/
|
||||
|
||||
use Drupal\Component\Utility\SafeMarkup;
|
||||
use Drupal\user\Entity\User;
|
||||
|
||||
/**
|
||||
* @addtogroup hooks
|
||||
* @{
|
||||
*/
|
||||
|
||||
/**
|
||||
* Provide replacement values for placeholder tokens.
|
||||
*
|
||||
* This hook is invoked when someone calls
|
||||
* \Drupal\Core\Utility\Token::replace(). That function first scans the text for
|
||||
* [type:token] patterns, and splits the needed tokens into groups by type.
|
||||
* Then hook_tokens() is invoked on each token-type group, allowing your module
|
||||
* to respond by providing replacement text for any of the tokens in the group
|
||||
* that your module knows how to process.
|
||||
*
|
||||
* A module implementing this hook should also implement hook_token_info() in
|
||||
* order to list its available tokens on editing screens.
|
||||
*
|
||||
* @param $type
|
||||
* The machine-readable name of the type (group) of token being replaced, such
|
||||
* as 'node', 'user', or another type defined by a hook_token_info()
|
||||
* implementation.
|
||||
* @param $tokens
|
||||
* An array of tokens to be replaced. The keys are the machine-readable token
|
||||
* names, and the values are the raw [type:token] strings that appeared in the
|
||||
* original text.
|
||||
* @param $data
|
||||
* (optional) An associative array of data objects to be used when generating
|
||||
* replacement values, as supplied in the $data parameter to
|
||||
* \Drupal\Core\Utility\Token::replace().
|
||||
* @param $options
|
||||
* (optional) An associative array of options for token replacement; see
|
||||
* \Drupal\Core\Utility\Token::replace() for possible values.
|
||||
*
|
||||
* @return
|
||||
* An associative array of replacement values, keyed by the raw [type:token]
|
||||
* strings from the original text.
|
||||
*
|
||||
* @see hook_token_info()
|
||||
* @see hook_tokens_alter()
|
||||
*/
|
||||
function hook_tokens($type, $tokens, array $data = array(), array $options = array()) {
|
||||
$token_service = \Drupal::token();
|
||||
|
||||
$url_options = array('absolute' => TRUE);
|
||||
if (isset($options['langcode'])) {
|
||||
$url_options['language'] = \Drupal::languageManager()->getLanguage($options['langcode']);
|
||||
$langcode = $options['langcode'];
|
||||
}
|
||||
else {
|
||||
$langcode = NULL;
|
||||
}
|
||||
$sanitize = !empty($options['sanitize']);
|
||||
|
||||
$replacements = array();
|
||||
|
||||
if ($type == 'node' && !empty($data['node'])) {
|
||||
/** @var \Drupal\node\NodeInterface $node */
|
||||
$node = $data['node'];
|
||||
|
||||
foreach ($tokens as $name => $original) {
|
||||
switch ($name) {
|
||||
// Simple key values on the node.
|
||||
case 'nid':
|
||||
$replacements[$original] = $node->nid;
|
||||
break;
|
||||
|
||||
case 'title':
|
||||
$replacements[$original] = $sanitize ? SafeMarkup::checkPlain($node->getTitle()) : $node->getTitle();
|
||||
break;
|
||||
|
||||
case 'edit-url':
|
||||
$replacements[$original] = $node->url('edit-form', $url_options);
|
||||
break;
|
||||
|
||||
// Default values for the chained tokens handled below.
|
||||
case 'author':
|
||||
$account = $node->getOwner() ? $node->getOwner() : User::load(0);
|
||||
$replacements[$original] = $sanitize ? SafeMarkup::checkPlain($account->label()) : $account->label();
|
||||
break;
|
||||
|
||||
case 'created':
|
||||
$replacements[$original] = format_date($node->getCreatedTime(), 'medium', '', NULL, $langcode);
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if ($author_tokens = $token_service->findWithPrefix($tokens, 'author')) {
|
||||
$replacements = $token_service->generate('user', $author_tokens, array('user' => $node->getOwner()), $options);
|
||||
}
|
||||
|
||||
if ($created_tokens = $token_service->findWithPrefix($tokens, 'created')) {
|
||||
$replacements = $token_service->generate('date', $created_tokens, array('date' => $node->getCreatedTime()), $options);
|
||||
}
|
||||
}
|
||||
|
||||
return $replacements;
|
||||
}
|
||||
|
||||
/**
|
||||
* Alter replacement values for placeholder tokens.
|
||||
*
|
||||
* @param $replacements
|
||||
* An associative array of replacements returned by hook_tokens().
|
||||
* @param $context
|
||||
* The context in which hook_tokens() was called. An associative array with
|
||||
* the following keys, which have the same meaning as the corresponding
|
||||
* parameters of hook_tokens():
|
||||
* - 'type'
|
||||
* - 'tokens'
|
||||
* - 'data'
|
||||
* - 'options'
|
||||
*
|
||||
* @see hook_tokens()
|
||||
*/
|
||||
function hook_tokens_alter(array &$replacements, array $context) {
|
||||
$options = $context['options'];
|
||||
|
||||
if (isset($options['langcode'])) {
|
||||
$url_options['language'] = \Drupal::languageManager()->getLanguage($options['langcode']);
|
||||
$langcode = $options['langcode'];
|
||||
}
|
||||
else {
|
||||
$langcode = NULL;
|
||||
}
|
||||
|
||||
if ($context['type'] == 'node' && !empty($context['data']['node'])) {
|
||||
$node = $context['data']['node'];
|
||||
|
||||
// Alter the [node:title] token, and replace it with the rendered content
|
||||
// of a field (field_title).
|
||||
if (isset($context['tokens']['title'])) {
|
||||
$title = $node->field_title->view('default');
|
||||
$replacements[$context['tokens']['title']] = drupal_render($title);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Provide information about available placeholder tokens and token types.
|
||||
*
|
||||
* Tokens are placeholders that can be put into text by using the syntax
|
||||
* [type:token], where type is the machine-readable name of a token type, and
|
||||
* token is the machine-readable name of a token within this group. This hook
|
||||
* provides a list of types and tokens to be displayed on text editing screens,
|
||||
* so that people editing text can see what their token options are.
|
||||
*
|
||||
* The actual token replacement is done by
|
||||
* \Drupal\Core\Utility\Token::replace(), which invokes hook_tokens(). Your
|
||||
* module will need to implement that hook in order to generate token
|
||||
* replacements from the tokens defined here.
|
||||
*
|
||||
* @return
|
||||
* An associative array of available tokens and token types. The outer array
|
||||
* has two components:
|
||||
* - types: An associative array of token types (groups). Each token type is
|
||||
* an associative array with the following components:
|
||||
* - name: The translated human-readable short name of the token type.
|
||||
* - description (optional): A translated longer description of the token
|
||||
* type.
|
||||
* - needs-data: The type of data that must be provided to
|
||||
* \Drupal\Core\Utility\Token::replace() in the $data argument (i.e., the
|
||||
* key name in $data) in order for tokens of this type to be used in the
|
||||
* $text being processed. For instance, if the token needs a node object,
|
||||
* 'needs-data' should be 'node', and to use this token in
|
||||
* \Drupal\Core\Utility\Token::replace(), the caller needs to supply a
|
||||
* node object as $data['node']. Some token data can also be supplied
|
||||
* indirectly; for instance, a node object in $data supplies a user object
|
||||
* (the author of the node), allowing user tokens to be used when only
|
||||
* a node data object is supplied.
|
||||
* - tokens: An associative array of tokens. The outer array is keyed by the
|
||||
* group name (the same key as in the types array). Within each group of
|
||||
* tokens, each token item is keyed by the machine name of the token, and
|
||||
* each token item has the following components:
|
||||
* - name: The translated human-readable short name of the token.
|
||||
* - description (optional): A translated longer description of the token.
|
||||
* - type (optional): A 'needs-data' data type supplied by this token, which
|
||||
* should match a 'needs-data' value from another token type. For example,
|
||||
* the node author token provides a user object, which can then be used
|
||||
* for token replacement data in \Drupal\Core\Utility\Token::replace()
|
||||
* without having to supply a separate user object.
|
||||
*
|
||||
* @see hook_token_info_alter()
|
||||
* @see hook_tokens()
|
||||
*/
|
||||
function hook_token_info() {
|
||||
$type = array(
|
||||
'name' => t('Nodes'),
|
||||
'description' => t('Tokens related to individual nodes.'),
|
||||
'needs-data' => 'node',
|
||||
);
|
||||
|
||||
// Core tokens for nodes.
|
||||
$node['nid'] = array(
|
||||
'name' => t("Node ID"),
|
||||
'description' => t("The unique ID of the node."),
|
||||
);
|
||||
$node['title'] = array(
|
||||
'name' => t("Title"),
|
||||
);
|
||||
$node['edit-url'] = array(
|
||||
'name' => t("Edit URL"),
|
||||
'description' => t("The URL of the node's edit page."),
|
||||
);
|
||||
|
||||
// Chained tokens for nodes.
|
||||
$node['created'] = array(
|
||||
'name' => t("Date created"),
|
||||
'type' => 'date',
|
||||
);
|
||||
$node['author'] = array(
|
||||
'name' => t("Author"),
|
||||
'type' => 'user',
|
||||
);
|
||||
|
||||
return array(
|
||||
'types' => array('node' => $type),
|
||||
'tokens' => array('node' => $node),
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Alter the metadata about available placeholder tokens and token types.
|
||||
*
|
||||
* @param $data
|
||||
* The associative array of token definitions from hook_token_info().
|
||||
*
|
||||
* @see hook_token_info()
|
||||
*/
|
||||
function hook_token_info_alter(&$data) {
|
||||
// Modify description of node tokens for our site.
|
||||
$data['tokens']['node']['nid'] = array(
|
||||
'name' => t("Node ID"),
|
||||
'description' => t("The unique ID of the article."),
|
||||
);
|
||||
$data['tokens']['node']['title'] = array(
|
||||
'name' => t("Title"),
|
||||
'description' => t("The title of the article."),
|
||||
);
|
||||
|
||||
// Chained tokens for nodes.
|
||||
$data['tokens']['node']['created'] = array(
|
||||
'name' => t("Date created"),
|
||||
'description' => t("The date the article was posted."),
|
||||
'type' => 'date',
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* @} End of "addtogroup hooks".
|
||||
*/
|
Reference in a new issue