TRUE]; $all_options = ['needs_environment' => TRUE, 'needs_context' => TRUE]; return [ new \Twig_SimpleFunction('drupal_view', 'views_embed_view'), new \Twig_SimpleFunction('drupal_view_result', 'views_get_view_result'), 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_entity_form', [$this, 'drupalEntityForm']), 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_image', [$this, 'drupalImage']), new \Twig_SimpleFunction('drupal_token', [$this, 'drupalToken']), new \Twig_SimpleFunction('drupal_config', [$this, 'drupalConfig']), new \Twig_SimpleFunction('drupal_dump', [$this, 'drupalDump'], $context_options), new \Twig_SimpleFunction('dd', [$this, 'drupalDump'], $context_options), new \Twig_SimpleFunction('drupal_title', [$this, 'drupalTitle']), new \Twig_SimpleFunction('drupal_url', [$this, 'drupalUrl']), new \Twig_SimpleFunction('drupal_link', [$this, 'drupalLink']), new \Twig_SimpleFunction('drupal_messages', [$this, 'drupalMessages']), new \Twig_SimpleFunction('drupal_breadcrumb', [$this, 'drupalBreadcrumb']), new \Twig_SimpleFunction('drupal_breakpoint', [$this, 'drupalBreakpoint'], $all_options), new \Twig_SimpleFunction('contextual_links', [$this, 'contextualLInks']), ]; } /** * {@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']), new \Twig_SimpleFilter('truncate', [$this, 'truncate']), new \Twig_SimpleFilter('view', [$this, 'view']), new \Twig_SimpleFilter('with', [$this, 'with']), new \Twig_SimpleFilter('children', [$this, 'children']), new \Twig_SimpleFilter('file_url', [$this, 'fileUrl']), ]; // 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 a block. * * In order to list all registered plugin IDs fetch them with block plugin * manager. With Drush it can be done like follows: * @code * drush ev "print_r(array_keys(\Drupal::service('plugin.manager.block')->getDefinitions()));" * @endcode * * Examples: * @code * {# Print block using default configuration. #} * {{ drupal_block('system_branding_block') }} * * {# Print block using custom configuration. #} * {{ drupal_block('system_branding_block', {label: 'Branding', use_site_name: false}) * * {# Bypass block.html.twig theming. #} * {{ drupal_block('system_branding_block', wrapper=false) }} * @endcode * * @see https://www.drupal.org/node/2964457#block-plugin * * @param mixed $id * The string of block plugin to render. * @param array $configuration * (optional) Pass on any configuration to the plugin block. * @param bool $wrapper * (optional) Whether or not use block template for rendering. * * @return null|array * A render array for the block or NULL if the block cannot be rendered. */ public function drupalBlock($id, array $configuration = [], $wrapper = TRUE) { $configuration += ['label_display' => BlockPluginInterface::BLOCK_LABEL_VISIBLE]; /** @var \Drupal\Core\Block\BlockPluginInterface $block_plugin */ $block_plugin = \Drupal::service('plugin.manager.block') ->createInstance($id, $configuration); // Inject runtime contexts. if ($block_plugin instanceof ContextAwarePluginInterface) { $contexts = \Drupal::service('context.repository')->getRuntimeContexts($block_plugin->getContextMapping()); \Drupal::service('context.handler')->applyContextMapping($block_plugin, $contexts); } if (!$block_plugin->access(\Drupal::currentUser())) { return; } // Title block needs special treatment. if ($block_plugin instanceof TitleBlockPluginInterface) { $request = \Drupal::request(); $route_match = \Drupal::routeMatch(); $title = \Drupal::service('title_resolver')->getTitle($request, $route_match->getRouteObject()); $block_plugin->setTitle($title); } $build = [ 'content' => $block_plugin->build(), '#cache' => [ 'contexts' => $block_plugin->getCacheContexts(), 'tags' => $block_plugin->getCacheTags(), 'max-age' => $block_plugin->getCacheMaxAge(), ], ]; if ($block_plugin instanceof TitleBlockPluginInterface) { $build['#cache']['contexts'][] = 'url'; } if ($wrapper && !Element::isEmpty($build['content'])) { $build += [ '#theme' => 'block', '#attributes' => [], '#contextual_links' => [], '#configuration' => $block_plugin->getConfiguration(), '#plugin_id' => $block_plugin->getPluginId(), '#base_plugin_id' => $block_plugin->getBaseId(), '#derivative_plugin_id' => $block_plugin->getDerivativeId(), ]; } return $build; } /** * Builds the render array of a given region. * * Examples: * @code * {# Print 'Sidebar First' region of the default site theme. #} * {{ drupal_region('sidebar_first') }} * * {# Print 'Sidebar First' region of Bartik theme. #} * {{ drupal_region('sidebar_first', 'bartik') }} * @endcode * * @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 = []; /* @var $blocks \Drupal\block\BlockInterface[] */ foreach ($blocks as $id => $block) { if ($block->access('view')) { $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) { $build['#region'] = $region; $build['#theme_wrappers'] = ['region']; } return $build; } /** * Returns the render array to represent and entity. * * Examples: * @code * {# Print a content block which ID is 1. #} * {{ drupal_entity('block_content', 1) }} * * {# Print a node's teaser. #} * {{ drupal_entity('node', 123, 'teaser') }} * * {# Print Branding block which was previously disabled on #} * {# admin/structure/block page. #} * {{ drupal_entity('block', 'bartik_branding', check_access=false) }} * @endcode * * @param string $entity_type * The entity type. * @param mixed $id * (optional) The ID of the entity to build. * @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. * @param bool $check_access * (optional) Indicates that access check is required. * * @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, $check_access = TRUE) { $entity_type_manager = \Drupal::entityTypeManager(); if ($id) { $entity = $entity_type_manager->getStorage($entity_type)->load($id); } else { @trigger_error('Loading entities from route is deprecated in Twig Tweak 2.4 and will not be supported in Twig Tweak 3.0', E_USER_DEPRECATED); $entity = \Drupal::routeMatch()->getParameter($entity_type); } if ($entity && (!$check_access || $entity->access('view'))) { $render_controller = $entity_type_manager->getViewBuilder($entity_type); return $render_controller->view($entity, $view_mode, $langcode); } } /** * Gets the built and processed entity form for the given entity type. * * Examples: * @code * {# Print edit form for node 1. #} * {{ drupal_entity_form('node', 1) }} * * {# Print add form for Article content type. #} * {{ drupal_entity_form('node', values={type: 'article'}) }} * * {# Print user register form. #} * {{ drupal_entity_form('user', NULL, 'register', check_access=false) }} * @endcode * * @param string $entity_type * The entity type. * @param mixed $id * (optional) The ID of the entity to build. If empty then new entity will * be created. * @param string $form_mode * (optional) The mode identifying the form variation to be returned. * @param array $values * (optional) An array of values to set, keyed by property name. * @param bool $check_access * (optional) Indicates that access check is required. * * @return array * The processed form for the given entity type and form mode. */ public function drupalEntityForm($entity_type, $id = NULL, $form_mode = 'default', array $values = [], $check_access = TRUE) { $entity_storage = \Drupal::entityTypeManager()->getStorage($entity_type); if ($id) { $entity = $entity_storage->load($id); $operation = 'update'; } else { $entity = $entity_storage->create($values); $operation = 'create'; } if ($entity && (!$check_access || $entity->access($operation))) { return \Drupal::service('entity.form_builder')->getForm($entity, $form_mode); } } /** * Returns the render array for a single entity field. * * Example: * @code * {{ drupal_field('field_image', 'node', 1) }} * {{ drupal_field('field_image', 'node', 1, 'teaser') }} * {{ drupal_field('field_image', 'node', 1, {type: 'image_url', settings: {image_style: 'large'}}) }} * @endcode * * @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. * @param bool $check_access * (optional) Indicates that access check is required. * * @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, $check_access = TRUE) { if ($id) { $entity = \Drupal::entityTypeManager()->getStorage($entity_type)->load($id); } else { @trigger_error('Loading entities from route is deprecated in Twig Tweak 2.4 and will not be supported in Twig Tweak 3.0', E_USER_DEPRECATED); $entity = \Drupal::routeMatch()->getParameter($entity_type); } if ($entity && (!$check_access || $entity->access('view'))) { $entity = \Drupal::service('entity.repository') ->getTranslationFromContext($entity, $langcode); if (isset($entity->{$field_name})) { return $entity->{$field_name}->view($view_mode); } } } /** * Returns the render array for Drupal menu. * * Example: * @code * {{ drupal_menu('main') }} * @endcode * * @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. * @param bool $expand * (optional) Expand all menu links. * * @return array * A render array for the menu. */ public function drupalMenu($menu_name, $level = 1, $depth = 0, $expand = FALSE) { /** @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())); } // If expandedParents is empty, the whole menu tree is built. if ($expand) { $parameters->expandedParents = []; } $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. * * Example: * @code * {{ drupal_form('Drupal\\search\\Form\\SearchBlockForm') }} * @endcode * * @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) { $callback = [\Drupal::formBuilder(), 'getForm']; return call_user_func_array($callback, func_get_args()); } /** * Builds an image. * * Examples: * @code * {# Render image specified by file ID. #} * {{ drupal_image(123) }} * * {# Render image specified by file UUID. #} * {{ drupal_image('9bb27144-e6b2-4847-bd24-adcc59613ec0') }} * * {# Render image specified by file URI. #} * {{ drupal_image('public://ocean.jpg') }} * * {# Render image using 'thumbnail' image style and custom attributes. #} * {{ drupal_image('public://ocean.jpg', 'thumbnail', {alt: 'The alternative text'|t, title: 'The title text'|t}) }} * * {# Render responsive image. #} * {{ drupal_image('public://ocean.jpg', 'wide', responsive=true) }} * @endcode * * @param mixed $property * A property to identify the image. * @param string $style * (optional) Image style. * @param array $attributes * (optional) Image attributes. * @param bool $responsive * (optional) Indicates that the provided image style is responsive. * @param bool $check_access * (optional) Indicates that access check is required. * * @return array|null * A render array to represent the image. */ public function drupalImage($property, $style = NULL, array $attributes = [], $responsive = FALSE, $check_access = TRUE) { // Determine property type by its value. if (preg_match('/^\d+$/', $property)) { $property_type = 'fid'; } elseif (Uuid::isValid($property)) { $property_type = 'uuid'; } else { $property_type = 'uri'; } $files = \Drupal::entityTypeManager() ->getStorage('file') ->loadByProperties([$property_type => $property]); // To avoid ambiguity render nothing unless exact one image was found. if (count($files) != 1) { return; } $file = reset($files); if ($check_access && !$file->access('view')) { return; } $build = [ '#uri' => $file->getFileUri(), '#attributes' => $attributes, ]; if ($style) { if ($responsive) { $build['#type'] = 'responsive_image'; $build['#responsive_image_style_id'] = $style; } else { $build['#theme'] = 'image_style'; $build['#style_name'] = $style; } } else { $build['#theme'] = 'image'; } return $build; } /** * Replaces a given tokens with appropriate value. * * Example: * @code * {{ drupal_token('site:name') }} * @endcode * * @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); } /** * Retrieves data from a given configuration object. * * Example: * @code * {{ drupal_config('system.site', 'name') }} * @endcode * * @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. * * Examples: * @code * {# Basic usage. #} * {{ drupal_dump(var) }} * * {# Same as above but shorter. #} * {{ dd(var) }} * * {# Dump all available variables for the current template. #} * {{ dd() }} * @endcode * * @param array $context * Variables from the Twig template. * @param mixed $variable * (optional) The variable to dump. */ public function drupalDump(array $context, $variable = NULL) { $var_dumper = '\Symfony\Component\VarDumper\VarDumper'; if (class_exists($var_dumper)) { call_user_func($var_dumper . '::dump', func_num_args() == 1 ? $context : $variable); } else { trigger_error('Could not dump the variable because symfony/var-dumper component is not installed.', E_USER_WARNING); } } /** * 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 an internal path. * * Examples: * @code * {# Basic usage. #} * {{ drupal_url('node/1) }} * * {# Complex URL. #} * {{ drupal_url('node/1', {query: {foo: 'bar'}, fragment: 'example', absolute: true}) }} * @endcode * * @param string $user_input * User input for a link or path. * @param array $options * (optional) An array of options. * @param bool $check_access * (optional) Indicates that access check is required. * * @return \Drupal\Core\Url * A new Url object based on user input. * * @see \Drupal\Core\Url::fromUserInput() */ public function drupalUrl($user_input, array $options = [], $check_access = FALSE) { if (isset($options['langcode'])) { $language_manager = \Drupal::languageManager(); if ($language = $language_manager->getLanguage($options['langcode'])) { $options['language'] = $language; } } if (!in_array($user_input[0], ['/', '#', '?'])) { $user_input = '/' . $user_input; } $url = Url::fromUserInput($user_input, $options); if (!$check_access || $url->access()) { return $url; } } /** * Generates a link from an internal path. * * Examples: * @code * {# It supports the same options as drupal_url(), plus attributes. #} * {{ drupal_link('View'|t, 'node/1', {attributes: {target: '_blank'}}) }} * * {# This link will only be shown for privileged users. #} * {{ drupal_link('Example'|t, '/admin', check_access=true) }} * @endcode * * @param string $text * The text to be used for the link. * @param string $user_input * User input for a link or path. * @param array $options * (optional) An array of options. * @param bool $check_access * (optional) Indicates that access check is required. * * @return \Drupal\Core\Link * A new Link object. * * @see \Drupal\Core\Link::fromTextAndUrl() */ public function drupalLink($text, $user_input, array $options = [], $check_access = FALSE) { $url = $this->drupalUrl($user_input, $options, $check_access); if ($url) { // The text has been processed by twig already, convert it to a safe // object for the render system. // @see \Drupal\Core\Template\TwigExtension::getLink() if ($text instanceof \Twig_Markup) { $text = Markup::create($text); } return Link::fromTextAndUrl($text, $url); } } /** * Displays status messages. */ public function drupalMessages() { return ['#type' => 'status_messages']; } /** * Builds the breadcrumb. */ public function drupalBreadcrumb() { return \Drupal::service('breadcrumb') ->build(\Drupal::routeMatch()) ->toRenderable(); } /** * Builds contextual links. * * Examples: * @code * {# Basic usage. #} *