Drupal 8.0.0 beta 12. More info: https://www.drupal.org/node/2514176

This commit is contained in:
Pantheon Automation 2015-08-17 17:00:26 -07:00 committed by Greg Anderson
commit 9921556621
13277 changed files with 1459781 additions and 0 deletions

View 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;
}
}

View 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;
}
}

View 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);
}

View 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));
}
}

View 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);
}
}
}
}

View 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]);
}
}

View 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'];
}
}
}

View file

@ -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);
}

View 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 { }

View 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".
*/