TwigExtension.php in Twig Tweak 8
Same filename and directory in other branches
Namespace
Drupal\twig_tweakFile
src/TwigExtension.phpView source
<?php
namespace Drupal\twig_tweak;
use Drupal\Core\Access\AccessResult;
use Drupal\Core\Block\TitleBlockPluginInterface;
use Drupal\Core\Cache\CacheableMetadata;
use Drupal\Core\Entity\EntityInterface;
use Drupal\Core\Site\Settings;
use Drupal\Core\Url;
use Drupal\image\Entity\ImageStyle;
use Symfony\Cmf\Component\Routing\RouteObjectInterface;
/**
* Twig extension with some useful functions and filters.
*
* As version 1.7 all dependencies are instantiated on demand for performance
* reasons.
*/
class TwigExtension extends \Twig_Extension {
/**
* {@inheritdoc}
*/
public function getFunctions() {
return [
new \Twig_SimpleFunction('drupal_view', 'views_embed_view'),
new \Twig_SimpleFunction('drupal_block', [
$this,
'drupalBlock',
]),
new \Twig_SimpleFunction('drupal_region', [
$this,
'drupalRegion',
]),
new \Twig_SimpleFunction('drupal_entity', [
$this,
'drupalEntity',
]),
new \Twig_SimpleFunction('drupal_field', [
$this,
'drupalField',
]),
new \Twig_SimpleFunction('drupal_menu', [
$this,
'drupalMenu',
]),
new \Twig_SimpleFunction('drupal_form', [
$this,
'drupalForm',
]),
new \Twig_SimpleFunction('drupal_token', [
$this,
'drupalToken',
]),
new \Twig_SimpleFunction('drupal_config', [
$this,
'drupalConfig',
]),
new \Twig_SimpleFunction('drupal_dump', [
$this,
'drupalDump',
]),
new \Twig_SimpleFunction('dd', [
$this,
'drupalDump',
]),
// Wrap drupal_set_message() because it returns some value which is not
// suitable for Twig template.
new \Twig_SimpleFunction('drupal_set_message', [
$this,
'drupalSetMessage',
]),
new \Twig_SimpleFunction('drupal_title', [
$this,
'drupalTitle',
]),
new \Twig_SimpleFunction('drupal_url', [
$this,
'drupalUrl',
]),
];
}
/**
* {@inheritdoc}
*/
public function getFilters() {
$filters = [
new \Twig_SimpleFilter('token_replace', [
$this,
'tokenReplaceFilter',
]),
new \Twig_SimpleFilter('preg_replace', [
$this,
'pregReplaceFilter',
]),
new \Twig_SimpleFilter('image_style', [
$this,
'imageStyle',
]),
new \Twig_SimpleFilter('transliterate', [
$this,
'transliterate',
]),
new \Twig_SimpleFilter('check_markup', [
$this,
'checkMarkup',
]),
];
// PHP filter should be enabled in settings.php file.
if (Settings::get('twig_tweak_enable_php_filter')) {
$filters[] = new \Twig_SimpleFilter('php', [
$this,
'phpFilter',
]);
}
return $filters;
}
/**
* {@inheritdoc}
*/
public function getName() {
return 'twig_tweak';
}
/**
* Builds the render array for the provided block.
*
* @param mixed $id
* The ID of the block to render.
* @param bool $check_access
* (Optional) Indicates that access check is required.
*
* @return null|array
* A render array for the block or NULL if the block does not exist.
*/
public function drupalBlock($id, $check_access = TRUE) {
$entity_type_manager = \Drupal::entityTypeManager();
$block = $entity_type_manager
->getStorage('block')
->load($id);
if ($block) {
$access = $check_access ? $this
->entityAccess($block) : AccessResult::allowed();
if ($access
->isAllowed()) {
$build = $entity_type_manager
->getViewBuilder('block')
->view($block);
CacheableMetadata::createFromRenderArray($build)
->merge(CacheableMetadata::createFromObject($block))
->merge(CacheableMetadata::createFromObject($access))
->applyTo($build);
return $build;
}
}
}
/**
* Builds the render array of a given region.
*
* @param string $region
* The region to build.
* @param string $theme
* (Optional) The name of the theme to load the region. If it is not
* provided then default theme will be used.
*
* @return array
* A render array to display the region content.
*/
public function drupalRegion($region, $theme = NULL) {
$entity_type_manager = \Drupal::entityTypeManager();
$blocks = $entity_type_manager
->getStorage('block')
->loadByProperties([
'region' => $region,
'theme' => $theme ?: \Drupal::config('system.theme')
->get('default'),
]);
$view_builder = $entity_type_manager
->getViewBuilder('block');
$build = [];
$cache_metadata = new CacheableMetadata();
/* @var $blocks \Drupal\block\BlockInterface[] */
foreach ($blocks as $id => $block) {
$access = $this
->entityAccess($block);
$cache_metadata = $cache_metadata
->merge(CacheableMetadata::createFromObject($access));
if ($access
->isAllowed()) {
$block_plugin = $block
->getPlugin();
if ($block_plugin instanceof TitleBlockPluginInterface) {
$request = \Drupal::request();
if ($route = $request->attributes
->get(RouteObjectInterface::ROUTE_OBJECT)) {
$block_plugin
->setTitle(\Drupal::service('title_resolver')
->getTitle($request, $route));
}
}
$build[$id] = $view_builder
->view($block);
}
}
if ($build) {
$cache_metadata
->applyTo($build);
}
return $build;
}
/**
* Returns the render array for an entity.
*
* @param string $entity_type
* The entity type.
* @param mixed $id
* The ID of the entity to render.
* @param string $view_mode
* (optional) The view mode that should be used to render the entity.
* @param string $langcode
* (optional) For which language the entity should be rendered, defaults to
* the current content language.
*
* @return null|array
* A render array for the entity or NULL if the entity does not exist.
*/
public function drupalEntity($entity_type, $id = NULL, $view_mode = NULL, $langcode = NULL) {
$entity_type_manager = \Drupal::entityTypeManager();
$entity = $id ? $entity_type_manager
->getStorage($entity_type)
->load($id) : \Drupal::routeMatch()
->getParameter($entity_type);
if ($entity) {
$access = $this
->entityAccess($entity);
if ($access
->isAllowed()) {
$build = $entity_type_manager
->getViewBuilder($entity_type)
->view($entity, $view_mode, $langcode);
CacheableMetadata::createFromRenderArray($build)
->merge(CacheableMetadata::createFromObject($entity))
->merge(CacheableMetadata::createFromObject($access))
->applyTo($build);
return $build;
}
}
}
/**
* Returns the render array for a single entity field.
*
* @param string $field_name
* The field name.
* @param string $entity_type
* The entity type.
* @param mixed $id
* The ID of the entity to render.
* @param string $view_mode
* (optional) The view mode that should be used to render the field.
* @param string $langcode
* (optional) Language code to load translation.
*
* @return null|array
* A render array for the field or NULL if the value does not exist.
*/
public function drupalField($field_name, $entity_type, $id = NULL, $view_mode = 'default', $langcode = NULL) {
/** @var \Drupal\Core\Entity\ContentEntityInterface $entity */
$entity = $id ? \Drupal::entityTypeManager()
->getStorage($entity_type)
->load($id) : \Drupal::routeMatch()
->getParameter($entity_type);
if ($entity) {
$access = $this
->entityAccess($entity);
if ($access
->isAllowed()) {
if ($langcode && $entity
->hasTranslation($langcode)) {
$entity = $entity
->getTranslation($langcode);
}
if (isset($entity->{$field_name})) {
$build = $entity->{$field_name}
->view($view_mode);
CacheableMetadata::createFromRenderArray($build)
->merge(CacheableMetadata::createFromObject($access))
->merge(CacheableMetadata::createFromObject($entity))
->applyTo($build);
return $build;
}
}
}
}
/**
* Returns the render array for Drupal menu.
*
* @param string $menu_name
* The name of the menu.
* @param int $level
* (optional) Initial menu level.
* @param int $depth
* (optional) Maximum number of menu levels to display.
*
* @return array
* A render array for the menu.
*/
public function drupalMenu($menu_name, $level = 1, $depth = 0) {
/** @var \Drupal\Core\Menu\MenuLinkTreeInterface $menu_tree */
$menu_tree = \Drupal::service('menu.link_tree');
$parameters = $menu_tree
->getCurrentRouteMenuTreeParameters($menu_name);
// Adjust the menu tree parameters based on the block's configuration.
$parameters
->setMinDepth($level);
// When the depth is configured to zero, there is no depth limit. When depth
// is non-zero, it indicates the number of levels that must be displayed.
// Hence this is a relative depth that we must convert to an actual
// (absolute) depth, that may never exceed the maximum depth.
if ($depth > 0) {
$parameters
->setMaxDepth(min($level + $depth - 1, $menu_tree
->maxDepth()));
}
$tree = $menu_tree
->load($menu_name, $parameters);
$manipulators = [
[
'callable' => 'menu.default_tree_manipulators:checkAccess',
],
[
'callable' => 'menu.default_tree_manipulators:generateIndexAndSort',
],
];
$tree = $menu_tree
->transform($tree, $manipulators);
return $menu_tree
->build($tree);
}
/**
* Builds and processes a form for a given form ID.
*
* @param string $form_id
* The form ID.
* @param ...
* Additional arguments are passed to form constructor.
*
* @return array
* A render array to represent the form.
*/
public function drupalForm($form_id) {
$form_builder = \Drupal::formBuilder();
$args = func_get_args();
return call_user_func_array([
$form_builder,
'getForm',
], $args);
}
/**
* Replaces a given tokens with appropriate value.
*
* @param string $token
* A replaceable token.
* @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.
*
* @return string
* The token value.
*
* @see \Drupal\Core\Utility\Token::replace()
*/
public function drupalToken($token, array $data = [], array $options = []) {
return \Drupal::token()
->replace("[{$token}]", $data, $options);
}
/**
* Gets data from this configuration.
*
* @param string $name
* The name of the configuration object to construct.
* @param string $key
* A string that maps to a key within the configuration data.
*
* @return mixed
* The data that was requested.
*/
public function drupalConfig($name, $key) {
return \Drupal::config($name)
->get($key);
}
/**
* Dumps information about variables.
*/
public function drupalDump($var) {
$var_dumper = '\\Symfony\\Component\\VarDumper\\VarDumper';
if (class_exists($var_dumper)) {
call_user_func($var_dumper . '::dump', $var);
}
else {
trigger_error('Could not dump the variable because symfony/var-dumper component is not installed.', E_USER_WARNING);
}
}
/**
* An alias for self::drupalDump().
*
* @see \Drupal\twig_tweak\TwigExtension::drupalDump();
*/
public function dd() {
$this
->drupalDump(func_get_args());
}
/**
* Sets a message to display to the user.
*
* @param string|\Drupal\Component\Render\MarkupInterface $message
* (optional) The translated message to be displayed to the user.
* @param string $type
* (optional) The message's type. Defaults to 'status'.
* @param bool $repeat
* (optional) If this is FALSE and the message is already set, then the
* message will not be repeated. Defaults to FALSE.
*
* @return array
* A render array to disable caching.
*/
public function drupalSetMessage($message = NULL, $type = 'status', $repeat = FALSE) {
\Drupal::messenger()
->addMessage($message, $type, $repeat);
$build['#cache']['max-age'] = 0;
return $build;
}
/**
* Returns a title for the current route.
*
* @return array
* A render array to represent page title.
*/
public function drupalTitle() {
$title = \Drupal::service('title_resolver')
->getTitle(\Drupal::request(), \Drupal::routeMatch()
->getRouteObject());
$build['#markup'] = render($title);
$build['#cache']['contexts'] = [
'url',
];
return $build;
}
/**
* Generates a URL from internal path.
*
* @param string $user_input
* User input for a link or path.
* @param array $options
* (optional) An array of options.
*
* @return \Drupal\Core\Url
* A new Url object based on user input.
*
* @see \Drupal\Core\Url::fromUserInput()
*/
public function drupalUrl($user_input, array $options = []) {
if (!in_array($user_input[0], [
'/',
'#',
'?',
])) {
$user_input = '/' . $user_input;
}
return Url::fromUserInput($user_input, $options);
}
/**
* Replaces all tokens in a given string with appropriate values.
*
* @param string $text
* An HTML string containing replaceable tokens.
*
* @return string
* The entered HTML text with tokens replaced.
*/
public function tokenReplaceFilter($text) {
return \Drupal::token()
->replace($text);
}
/**
* Performs a regular expression search and replace.
*
* @param string $text
* The text to search and replace.
* @param string $pattern
* The pattern to search for.
* @param string $replacement
* The string to replace.
*
* @return string
* The new text if matches are found, otherwise unchanged text.
*/
public function pregReplaceFilter($text, $pattern, $replacement) {
// BC layer. Before version 8.x-1.8 the pattern was without delimiters.
// @todo Remove this in Drupal 9.
if (strpos($pattern, '/') !== 0) {
return preg_replace("/{$pattern}/", $replacement, $text);
}
return preg_replace($pattern, $replacement, $text);
}
/**
* Returns the URL of this image derivative for an original image path or URI.
*
* @param string $path
* The path or URI to the original image.
* @param string $style
* The image style.
*
* @return string
* The absolute URL where a style image can be downloaded, suitable for use
* in an <img> tag. Requesting the URL will cause the image to be created.
*/
public function imageStyle($path, $style) {
/** @var \Drupal\Image\ImageStyleInterface $image_style */
if ($image_style = ImageStyle::load($style)) {
return file_url_transform_relative($image_style
->buildUrl($path));
}
}
/**
* Transliterates text from Unicode to US-ASCII.
*
* @param string $string
* The string to transliterate.
* @param string $langcode
* (optional) The language code of the language the string is in. Defaults
* to 'en' if not provided. Warning: this can be unfiltered user input.
* @param string $unknown_character
* (optional) The character to substitute for characters in $string without
* transliterated equivalents. Defaults to '?'.
* @param int $max_length
* (optional) If provided, return at most this many characters, ensuring
* that the transliteration does not split in the middle of an input
* character's transliteration.
*
* @return string
* $string with non-US-ASCII characters transliterated to US-ASCII
* characters, and unknown characters replaced with $unknown_character.
*/
public function transliterate($string, $langcode = 'en', $unknown_character = '?', $max_length = NULL) {
return \Drupal::transliteration()
->transliterate($string, $langcode, $unknown_character, $max_length);
}
/**
* Runs all the enabled filters on a piece of text.
*
* @param string $text
* The text to be filtered.
* @param string|null $format_id
* (optional) The machine name of the filter format to be used to filter the
* text. Defaults to the fallback format. See filter_fallback_format().
* @param string $langcode
* (optional) The language code of the text to be filtered.
* @param array $filter_types_to_skip
* (optional) An array of filter types to skip, or an empty array (default)
* to skip no filter types.
*
* @return \Drupal\Component\Render\MarkupInterface
* The filtered text.
*
* @see check_markup()
*/
public function checkMarkup($text, $format_id = NULL, $langcode = '', array $filter_types_to_skip = []) {
return check_markup($text, $format_id, $langcode, $filter_types_to_skip);
}
/**
* Evaluates a string of PHP code.
*
* @param string $code
* Valid PHP code to be evaluated.
*
* @return mixed
* The eval() result.
*/
public function phpFilter($code) {
ob_start();
// @codingStandardsIgnoreStart
print eval($code);
// @codingStandardsIgnoreEnd
$output = ob_get_contents();
ob_end_clean();
return $output;
}
/**
* Checks view access to a given entity.
*
* @param \Drupal\Core\Entity\EntityInterface $entity
* Entity to check access.
*
* @return \Drupal\Core\Access\AccessResultInterface
* The access check result.
*
* @TODO Remove "check_access" option in 9.x.
*/
protected function entityAccess(EntityInterface $entity) {
// Prior version 8.x-1.7 entity access was not checked. The "check_access"
// option provides a workaround for possible BC issues.
return Settings::get('twig_tweak_check_access', TRUE) ? $entity
->access('view', NULL, TRUE) : AccessResult::allowed();
}
}
Classes
Name | Description |
---|---|
TwigExtension | Twig extension with some useful functions and filters. |