/** * {@inheritdoc} */ public function getVisibleBlocksPerRegion(array &$cacheable_metadata = []) { $active_theme = $this->themeManager->getActiveTheme(); // Build an array of the region names in the right order. $empty = array_fill_keys($active_theme->getRegions(), array()); $full = array(); foreach ($this->blockStorage->loadByProperties(array('theme' => $active_theme->getName())) as $block_id => $block) { /** @var \Drupal\block\BlockInterface $block */ $access = $block->access('view', NULL, TRUE); $region = $block->getRegion(); if (!isset($cacheable_metadata[$region])) { $cacheable_metadata[$region] = CacheableMetadata::createFromObject($access); } else { $cacheable_metadata[$region] = $cacheable_metadata[$region]->merge(CacheableMetadata::createFromObject($access)); } // Set the contexts on the block before checking access. if ($access->isAllowed()) { $full[$region][$block_id] = $block; } } // Merge it with the actual values to maintain the region ordering. $assignments = array_intersect_key(array_merge($empty, $full), $empty); foreach ($assignments as &$assignment) { // Suppress errors because PHPUnit will indirectly modify the contents, // triggering https://bugs.php.net/bug.php?id=50688. @uasort($assignment, 'Drupal\\block\\Entity\\Block::sort'); } return $assignments; }
/** * Creates a bubbleable metadata object from a depended object. * * @param \Drupal\Core\Cache\CacheableDependencyInterface|mixed $object * The object whose cacheability metadata to retrieve. If it implements * CacheableDependencyInterface, its cacheability metadata will be used, * otherwise, the passed in object must be assumed to be uncacheable, so * max-age 0 is set. * * @return static */ public static function createFromObject($object) { $meta = parent::createFromObject($object); if ($object instanceof AttachmentsInterface) { $meta->attachments = $object->getAttachments(); } return $meta; }
/** * {@inheritdoc} */ public function buildSelectorForm(array $form, FormStateInterface $form_state) { $form = parent::buildSelectorForm($form, $form_state); $available_plugins = []; $cacheability_metadata = CacheableMetadata::createFromRenderArray($form); foreach (array_keys($this->selectablePluginDiscovery->getDefinitions()) as $plugin_id) { $available_plugin = $this->selectablePluginFactory->createInstance($plugin_id); $available_plugins[] = $available_plugin; $cacheability_metadata = $cacheability_metadata->merge(CacheableMetadata::createFromObject($available_plugin)); } $cacheability_metadata->applyTo($form); $plugin_selector_form_state_key = static::setPluginSelector($form_state, $this); $form['container'] = array('#attributes' => array('class' => array('plugin-selector-' . Html::getClass($this->getPluginId()))), '#available_plugins' => $available_plugins, '#plugin_selector_form_state_key' => $plugin_selector_form_state_key, '#process' => [[get_class(), 'processBuildSelectorForm']], '#tree' => TRUE, '#type' => 'container'); return $form; }
/** * Returns the referenced entities for display. * * The method takes care of: * - checking entity access, * - placing the entities in the language expected for display. * It is thus strongly recommended that formatters use it in their * implementation of viewElements($items) rather than dealing with $items * directly. * * For each entity, the EntityReferenceItem by which the entity is referenced * is available in $entity->_referringItem. This is useful for field types * that store additional values next to the reference itself. * * @param \Drupal\Core\Field\EntityReferenceFieldItemListInterface $items * The item list. * @param string $langcode * The language code of the referenced entities to display. * * @return \Drupal\Core\Entity\EntityInterface[] * The array of referenced entities to display, keyed by delta. * * @see ::prepareView() */ protected function getEntitiesToView(EntityReferenceFieldItemListInterface $items, $langcode) { $entities = array(); foreach ($items as $delta => $item) { // Ignore items where no entity could be loaded in prepareView(). if (!empty($item->_loaded)) { $entity = $item->entity; // Set the entity in the correct language for display. if ($entity instanceof TranslatableInterface) { $entity = \Drupal::entityManager()->getTranslationFromContext($entity, $langcode); } $access = $this->checkAccess($entity); // Add the access result's cacheability, ::view() needs it. $item->_accessCacheability = CacheableMetadata::createFromObject($access); if ($access->isAllowed()) { // Add the referring item, in case the formatter needs it. $entity->_referringItem = $items[$delta]; $entities[$delta] = $entity; } } } return $entities; }
/** * @covers ::createFromObject * @dataProvider providerTestCreateFromObject */ public function testCreateFromObject($object, CacheableMetadata $expected) { $this->assertEquals($expected, CacheableMetadata::createFromObject($object)); }
/** * Builds the #items property for a menu tree's renderable array. * * Helper function for ::build(). * * @param \Drupal\Core\Menu\MenuLinkTreeElement[] $tree * A data structure representing the tree, as returned from * MenuLinkTreeInterface::load(). * @param \Drupal\Core\Cache\CacheableMetadata &$tree_access_cacheability * Internal use only. The aggregated cacheability metadata for the access * results across the entire tree. Used when rendering the root level. * @param \Drupal\Core\Cache\CacheableMetadata &$tree_link_cacheability * Internal use only. The aggregated cacheability metadata for the menu * links across the entire tree. Used when rendering the root level. * * @return array * The value to use for the #items property of a renderable menu. * * @throws \DomainException */ protected function buildItems(array $tree, CacheableMetadata &$tree_access_cacheability, CacheableMetadata &$tree_link_cacheability) { $items = array(); foreach ($tree as $data) { /** @var \Drupal\Core\Menu\MenuLinkInterface $link */ $link = $data->link; // Generally we only deal with visible links, but just in case. if (!$link->isEnabled()) { continue; } if ($data->access !== NULL && !$data->access instanceof AccessResultInterface) { throw new \DomainException('MenuLinkTreeElement::access must be either NULL or an AccessResultInterface object.'); } // Gather the access cacheability of every item in the menu link tree, // including inaccessible items. This allows us to render cache the menu // tree, yet still automatically vary the rendered menu by the same cache // contexts that the access results vary by. // However, if $data->access is not an AccessResultInterface object, this // will still render the menu link, because this method does not want to // require access checking to be able to render a menu tree. if ($data->access instanceof AccessResultInterface) { $tree_access_cacheability = $tree_access_cacheability->merge(CacheableMetadata::createFromObject($data->access)); } // Gather the cacheability of every item in the menu link tree. Some links // may be dynamic: they may have a dynamic text (e.g. a "Hi, <user>" link // text, which would vary by 'user' cache context), or a dynamic route // name or route parameters. $tree_link_cacheability = $tree_link_cacheability->merge(CacheableMetadata::createFromObject($data->link)); // Only render accessible links. if ($data->access instanceof AccessResultInterface && !$data->access->isAllowed()) { continue; } $element = []; // Set a variable for the <li> tag. Only set 'expanded' to true if the // link also has visible children within the current tree. $element['is_expanded'] = FALSE; $element['is_collapsed'] = FALSE; if ($data->hasChildren && !empty($data->subtree)) { $element['is_expanded'] = TRUE; } elseif ($data->hasChildren) { $element['is_collapsed'] = TRUE; } // Set a helper variable to indicate whether the link is in the active // trail. $element['in_active_trail'] = FALSE; if ($data->inActiveTrail) { $element['in_active_trail'] = TRUE; } // Note: links are rendered in the menu.html.twig template; and they // automatically bubble their associated cacheability metadata. $element['attributes'] = new Attribute(); $element['title'] = $link->getTitle(); $element['url'] = $link->getUrlObject(); $element['url']->setOption('set_active_class', TRUE); $element['below'] = $data->subtree ? $this->buildItems($data->subtree, $tree_access_cacheability, $tree_link_cacheability) : array(); if (isset($data->options)) { $element['url']->setOptions(NestedArray::mergeDeep($element['url']->getOptions(), $data->options)); } $element['original_link'] = $link; // Index using the link's unique ID. $items[$link->getPluginId()] = $element; } return $items; }
/** * {@inheritdoc} */ public function addCacheableDependency(array &$elements, $dependency) { $meta_a = CacheableMetadata::createFromRenderArray($elements); $meta_b = CacheableMetadata::createFromObject($dependency); $meta_a->merge($meta_b)->applyTo($elements); }
/** * Embeds a Response object in a render array so that RenderCache can cache it. * * @param \Drupal\Core\Cache\CacheableResponseInterface $response * A cacheable response. * * @return array * A render array that embeds the given cacheable response object, with the * cacheability metadata of the response object present in the #cache * property of the render array. * * @see renderArrayToResponse() * * @todo Refactor/remove once https://www.drupal.org/node/2551419 lands. */ protected function responseToRenderArray(CacheableResponseInterface $response) { $response_as_render_array = $this->dynamicPageCacheRedirectRenderArray + ['#response' => $response, '#cache_properties' => ['#response'], '#markup' => '', '#attached' => '']; // Merge the response's cacheability metadata, so that RenderCache can take // care of cache redirects for us. CacheableMetadata::createFromObject($response->getCacheableMetadata())->merge(CacheableMetadata::createFromRenderArray($response_as_render_array))->applyTo($response_as_render_array); return $response_as_render_array; }
/** * {@inheritdoc} */ public function process($text, $langcode) { $result = new FilterProcessResult($text); if (strpos($text, 'data-entity-type') !== FALSE && (strpos($text, 'data-entity-embed-display') !== FALSE || strpos($text, 'data-view-mode') !== FALSE)) { $dom = Html::load($text); $xpath = new \DOMXPath($dom); foreach ($xpath->query('//drupal-entity[@data-entity-type and (@data-entity-uuid or @data-entity-id) and (@data-entity-embed-display or @data-view-mode)]') as $node) { /** @var \DOMElement $node */ $entity_type = $node->getAttribute('data-entity-type'); $entity = NULL; $entity_output = ''; try { // Load the entity either by UUID (preferred) or ID. $id = $node->getAttribute('data-entity-uuid') ?: $node->getAttribute('data-entity-id'); $entity = $this->loadEntity($entity_type, $id); if ($entity) { // Protect ourselves from recursive rendering. static $depth = 0; $depth++; if ($depth > 20) { throw new RecursiveRenderingException(sprintf('Recursive rendering detected when rendering embedded %s entity %s.', $entity_type, $entity->id())); } // If a UUID was not used, but is available, add it to the HTML. if (!$node->getAttribute('data-entity-uuid') && ($uuid = $entity->uuid())) { $node->setAttribute('data-entity-uuid', $uuid); } $access = $entity->access('view', NULL, TRUE); $access_metadata = CacheableMetadata::createFromObject($access); $entity_metadata = CacheableMetadata::createFromObject($entity); $result = $result->merge($entity_metadata)->merge($access_metadata); $context = $this->getNodeAttributesAsArray($node); $context += array('data-langcode' => $langcode); $entity_output = $this->renderEntityEmbed($entity, $context); $depth--; } else { throw new EntityNotFoundException(sprintf('Unable to load embedded %s entity %s.', $entity_type, $id)); } } catch (\Exception $e) { watchdog_exception('entity_embed', $e); } $this->replaceNodeContent($node, $entity_output); } $result->setProcessedText(Html::serialize($dom)); } return $result; }
/** * {@inheritdoc} */ public function calculateCacheMetadata() { $cache_metadata = new CacheableMetadata(); // Iterate over ordinary views plugins. foreach (Views::getPluginTypes('plugin') as $plugin_type) { $plugin = $this->getPlugin($plugin_type); if ($plugin instanceof CacheableDependencyInterface) { $cache_metadata = $cache_metadata->merge(CacheableMetadata::createFromObject($plugin)); } } // Iterate over all handlers. Note that at least the argument handler will // need to ask all its subplugins. foreach (array_keys(Views::getHandlerTypes()) as $handler_type) { $handlers = $this->getHandlers($handler_type); foreach ($handlers as $handler) { if ($handler instanceof CacheableDependencyInterface) { $cache_metadata = $cache_metadata->merge(CacheableMetadata::createFromObject($handler)); } } } /** @var \Drupal\views\Plugin\views\cache\CachePluginBase $cache_plugin */ if ($cache_plugin = $this->getPlugin('cache')) { $cache_plugin->alterCacheMetadata($cache_metadata); } return $cache_metadata; }
/** * {@inheritdoc} */ public function build() { /** @var $entity \Drupal\Core\Entity\EntityInterface */ $entity = $this->getContextValue('entity'); $view_builder = $this->entityManager->getViewBuilder($entity->getEntityTypeId()); $build = $view_builder->view($entity, $this->configuration['view_mode']); CacheableMetadata::createFromObject($this->getContext('entity'))->applyTo($build); return $build; }
/** * Asserts \Drupal\Core\Routing\UrlGenerator::generateFromRoute()'s output. * * @param $route_name * The route name to test. * @param array $route_parameters * The route parameters to test. * @param array $options * The options to test. * @param $expected_url * The expected generated URL string. * @param \Drupal\Core\Cache\CacheableMetadata $expected_cacheability * The expected generated cacheability metadata. */ protected function assertGenerateFromRoute($route_name, array $route_parameters, array $options, $expected_url, CacheableMetadata $expected_cacheability) { // First, test with $collect_cacheability_metadata set to the default value. $url = $this->generator->generateFromRoute($route_name, $route_parameters, $options); $this->assertSame($expected_url, $url); // Second, test with it set to TRUE. $generated_url = $this->generator->generateFromRoute($route_name, $route_parameters, $options, TRUE); $this->assertSame($expected_url, $generated_url->getGeneratedUrl()); $this->assertEquals($expected_cacheability, CacheableMetadata::createFromObject($generated_url)); }
/** * {@inheritdoc} */ public function addCacheableDependency($dependency) { $this->cacheabilityMetadata = $this->cacheabilityMetadata->merge(CacheableMetadata::createFromObject($dependency)); return $this; }
/** * {@inheritdoc} */ protected function doLoadMultiple(array $ids = NULL) { $prefix = $this->getPrefix(); // Get the names of the configuration entities we are going to load. if ($ids === NULL) { $names = $this->configFactory->listAll($prefix); } else { $names = array(); foreach ($ids as $id) { // Add the prefix to the ID to serve as the configuration object name. $names[] = $prefix . $id; } } // Load all of the configuration entities. /** @var \Drupal\Core\Config\Config[] $configs */ $configs = []; $records = []; foreach ($this->configFactory->loadMultiple($names) as $config) { $id = $config->get($this->idKey); $records[$id] = $this->overrideFree ? $config->getOriginal(NULL, FALSE) : $config->get(); $configs[$id] = $config; } $entities = $this->mapFromStorageRecords($records, $configs); // Config entities wrap config objects, and therefore they need to inherit // the cacheability metadata of config objects (to ensure e.g. additional // cacheability metadata added by config overrides is not lost). foreach ($entities as $id => $entity) { // But rather than simply inheriting all cacheability metadata of config // objects, we need to make sure the self-referring cache tag that is // present on Config objects is not added to the Config entity. It must be // removed for 3 reasons: // 1. When renaming/duplicating a Config entity, the cache tag of the // original config object would remain present, which would be wrong. // 2. Some Config entities choose to not use the cache tag that the under- // lying Config object provides by default (For performance and // cacheability reasons it may not make sense to have a unique cache // tag for every Config entity. The DateFormat Config entity specifies // the 'rendered' cache tag for example, because A) date formats are // changed extremely rarely, so invalidating all render cache items is // fine, B) it means fewer cache tags per page.). // 3. Fewer cache tags is better for performance. $self_referring_cache_tag = ['config:' . $configs[$id]->getName()]; $config_cacheability = CacheableMetadata::createFromObject($configs[$id]); $config_cacheability->setCacheTags(array_diff($config_cacheability->getCacheTags(), $self_referring_cache_tag)); $entity->addCacheableDependency($config_cacheability); } return $entities; }
/** * {@inheritdoc} */ protected function valueForm(&$form, FormStateInterface $form_state) { parent::valueForm($form, $form_state); // Apply cacheability metadata, because the parent class does not. // @todo Remove this once https://www.drupal.org/node/2754103 is fixed. $cacheability_metdata = CacheableMetadata::createFromObject($this); $cacheability_metdata->applyTo($form); return $form; }
/** * Build the render array for a single panelized entity. * * @param \Drupal\Core\Entity\EntityInterface $entity * @param \Drupal\panels\Plugin\DisplayVariant\PanelsDisplayVariant $panels_display * @param string $view_mode * @param string $langcode * * @return array */ protected function buildPanelized(EntityInterface $entity, PanelsDisplayVariant $panels_display, $view_mode, $langcode) { $contexts = $panels_display->getContexts(); $entity_context = new Context(new ContextDefinition('entity:' . $this->entityTypeId, NULL, TRUE), $entity); $contexts['@panelizer.entity_context:' . $this->entityTypeId] = $entity_context; $panels_display->setContexts($contexts); $build = $panels_display->build(); // @todo: I'm sure more is necessary to get the cache contexts right... CacheableMetadata::createFromObject($entity)->applyTo($build); $this->getPanelizerPlugin()->alterBuild($build, $entity, $panels_display, $view_mode); return $build; }
/** * Iterates over all items in the tree to prepare the parents select options. * * @param \Drupal\Core\Menu\MenuLinkTreeElement[] $tree * The menu tree. * @param string $menu_name * The menu name. * @param string $indent * The indentation string used for the label. * @param array $options * The select options. * @param string $exclude * An excluded menu link. * @param int $depth_limit * The maximum depth of menu links considered for the select options. * @param \Drupal\Core\Cache\CacheableMetadata|NULL &$cacheability * The object to add cacheability metadata to, if not NULL. */ protected function parentSelectOptionsTreeWalk(array $tree, $menu_name, $indent, array &$options, $exclude, $depth_limit, CacheableMetadata &$cacheability = NULL) { foreach ($tree as $element) { if ($element->depth > $depth_limit) { // Don't iterate through any links on this level. break; } // Collect the cacheability metadata of the access result, as well as the // link. if ($cacheability) { $cacheability = $cacheability->merge(CacheableMetadata::createFromObject($element->access))->merge(CacheableMetadata::createFromObject($element->link)); } // Only show accessible links. if (!$element->access->isAllowed()) { continue; } $link = $element->link; if ($link->getPluginId() != $exclude) { $title = $indent . ' ' . Unicode::truncate($link->getTitle(), 30, TRUE, FALSE); if (!$link->isEnabled()) { $title .= ' (' . $this->t('disabled') . ')'; } $options[$menu_name . ':' . $link->getPluginId()] = $title; if (!empty($element->subtree)) { $this->parentSelectOptionsTreeWalk($element->subtree, $menu_name, $indent . '--', $options, $exclude, $depth_limit, $cacheability); } } } }
/** * {@inheritdoc} */ public function buildRow(EntityInterface $entity) { $row['username']['data'] = array('#theme' => 'username', '#account' => $entity); $row['status'] = $entity->isActive() ? $this->t('active') : $this->t('blocked'); $roles = user_role_names(TRUE); unset($roles[RoleInterface::AUTHENTICATED_ID]); $users_roles = array(); foreach ($entity->getRoles() as $role) { if (isset($roles[$role])) { $users_roles[] = $roles[$role]; } } asort($users_roles); $row['roles']['data'] = array('#theme' => 'item_list', '#items' => $users_roles); $options = ['return_as_object' => TRUE]; $row['member_for']['data'] = $this->dateFormatter->formatTimeDiffSince($entity->getCreatedTime(), $options)->toRenderable(); $last_access = $this->dateFormatter->formatTimeDiffSince($entity->getLastAccessedTime(), $options); if ($entity->getLastAccessedTime()) { $row['access']['data']['#markup'] = $last_access->getString(); CacheableMetadata::createFromObject($last_access)->applyTo($row['access']['data']); } else { $row['access']['data']['#markup'] = t('never'); } return $row + parent::buildRow($entity); }
/** * {@inheritdoc} */ public function build() { $config = $this->getConfiguration(); if (empty($config['required_configuration'])) { throw new \Exception('Required configuration is missing!'); } $contexts = $this->getContexts(); if (!isset($contexts['context'])) { throw new \Exception('Required context is missing!'); } $build = []; $build['content']['default'] = ['#markup' => $config['required_configuration'] . ' ' . $contexts['context']->getContextValue()]; CacheableMetadata::createFromObject($this)->applyTo($build); return $build; }
/** * Formats a date/time as a time interval. * * @param \Drupal\Core\Datetime\DrupalDateTime|object $date * A date/time object. * * @return array * The formatted date/time string using the past or future format setting. */ protected function formatDate(DrupalDateTime $date) { $granularity = $this->getSetting('granularity'); $timestamp = $date->getTimestamp(); $options = ['granularity' => $granularity, 'return_as_object' => TRUE]; if ($this->request->server->get('REQUEST_TIME') > $timestamp) { $result = $this->dateFormatter->formatTimeDiffSince($timestamp, $options); $build = ['#markup' => SafeMarkup::format($this->getSetting('past_format'), ['@interval' => $result->getString()])]; } else { $result = $this->dateFormatter->formatTimeDiffUntil($timestamp, $options); $build = ['#markup' => SafeMarkup::format($this->getSetting('future_format'), ['@interval' => $result->getString()])]; } CacheableMetadata::createFromObject($result)->applyTo($build); return $build; }
/** * Recursive helper function for buildOverviewForm(). * * @param \Drupal\Core\Menu\MenuLinkTreeElement[] $tree * The tree retrieved by \Drupal\Core\Menu\MenuLinkTreeInterface::load(). * @param int $delta * The default number of menu items used in the menu weight selector is 50. * * @return array * The overview tree form. */ protected function buildOverviewTreeForm($tree, $delta) { $form =& $this->overviewTreeForm; $tree_access_cacheability = new CacheableMetadata(); foreach ($tree as $element) { $tree_access_cacheability = $tree_access_cacheability->merge(CacheableMetadata::createFromObject($element->access)); // Only render accessible links. if (!$element->access->isAllowed()) { continue; } /** @var \Drupal\Core\Menu\MenuLinkInterface $link */ $link = $element->link; if ($link) { $id = 'menu_plugin_id:' . $link->getPluginId(); $form[$id]['#item'] = $element; $form[$id]['#attributes'] = $link->isEnabled() ? array('class' => array('menu-enabled')) : array('class' => array('menu-disabled')); $form[$id]['title'] = Link::fromTextAndUrl($link->getTitle(), $link->getUrlObject())->toRenderable(); if (!$link->isEnabled()) { $form[$id]['title']['#suffix'] = ' (' . $this->t('disabled') . ')'; } elseif (($url = $link->getUrlObject()) && $url->isRouted() && $url->getRouteName() == 'user.page') { $form[$id]['title']['#suffix'] = ' (' . $this->t('logged in users only') . ')'; } $form[$id]['enabled'] = array('#type' => 'checkbox', '#title' => $this->t('Enable @title menu link', array('@title' => $link->getTitle())), '#title_display' => 'invisible', '#default_value' => $link->isEnabled()); $form[$id]['weight'] = array('#type' => 'weight', '#delta' => $delta, '#default_value' => $link->getWeight(), '#title' => $this->t('Weight for @title', array('@title' => $link->getTitle())), '#title_display' => 'invisible'); $form[$id]['id'] = array('#type' => 'hidden', '#value' => $link->getPluginId()); $form[$id]['parent'] = array('#type' => 'hidden', '#default_value' => $link->getParent()); // Build a list of operations. $operations = array(); $operations['edit'] = array('title' => $this->t('Edit')); // Allow for a custom edit link per plugin. $edit_route = $link->getEditRoute(); if ($edit_route) { $operations['edit']['url'] = $edit_route; // Bring the user back to the menu overview. $operations['edit']['query'] = $this->getDestinationArray(); } else { // Fall back to the standard edit link. $operations['edit'] += array('url' => Url::fromRoute('menu_ui.link_edit', ['menu_link_plugin' => $link->getPluginId()])); } // Links can either be reset or deleted, not both. if ($link->isResettable()) { $operations['reset'] = array('title' => $this->t('Reset'), 'url' => Url::fromRoute('menu_ui.link_reset', ['menu_link_plugin' => $link->getPluginId()])); } elseif ($delete_link = $link->getDeleteRoute()) { $operations['delete']['url'] = $delete_link; $operations['delete']['query'] = $this->getDestinationArray(); $operations['delete']['title'] = $this->t('Delete'); } if ($link->isTranslatable()) { $operations['translate'] = array('title' => $this->t('Translate'), 'url' => $link->getTranslateRoute()); } $form[$id]['operations'] = array('#type' => 'operations', '#links' => $operations); } if ($element->subtree) { $this->buildOverviewTreeForm($element->subtree, $delta); } } $tree_access_cacheability->merge(CacheableMetadata::createFromRenderArray($form))->applyTo($form); return $form; }
/** * Provide the administration overview page. * * @param string $link_id * The ID of the administrative path link for which to display child links. * * @return array * A renderable array of the administration overview page. */ public function overview($link_id) { // Check for status report errors. if ($this->systemManager->checkRequirements() && $this->currentUser()->hasPermission('administer site configuration')) { drupal_set_message($this->t('One or more problems were detected with your Drupal installation. Check the <a href="@status">status report</a> for more information.', array('@status' => $this->url('system.status'))), 'error'); } // Load all menu links below it. $parameters = new MenuTreeParameters(); $parameters->setRoot($link_id)->excludeRoot()->setTopLevelOnly()->onlyEnabledLinks(); $tree = $this->menuLinkTree->load(NULL, $parameters); $manipulators = array(array('callable' => 'menu.default_tree_manipulators:checkAccess'), array('callable' => 'menu.default_tree_manipulators:generateIndexAndSort')); $tree = $this->menuLinkTree->transform($tree, $manipulators); $tree_access_cacheability = new CacheableMetadata(); $blocks = array(); foreach ($tree as $key => $element) { $tree_access_cacheability = $tree_access_cacheability->merge(CacheableMetadata::createFromObject($element->access)); // Only render accessible links. if (!$element->access->isAllowed()) { continue; } $link = $element->link; $block['title'] = $link->getTitle(); $block['description'] = $link->getDescription(); $block['content'] = array('#theme' => 'admin_block_content', '#content' => $this->systemManager->getAdminBlock($link)); if (!empty($block['content']['#content'])) { $blocks[$key] = $block; } } if ($blocks) { ksort($blocks); $build = ['#theme' => 'admin_page', '#blocks' => $blocks]; $tree_access_cacheability->applyTo($build); return $build; } else { $build = ['#markup' => $this->t('You do not have any administrative items.')]; $tree_access_cacheability->applyTo($build); return $build; } }
/** * Builds the translations overview page. * * @param \Drupal\Core\Routing\RouteMatchInterface $route_match * The route match. * @param string $entity_type_id * (optional) The entity type ID. * @return array Array of page elements to render. * Array of page elements to render. */ public function overview(RouteMatchInterface $route_match, $entity_type_id = NULL) { /** @var \Drupal\Core\Entity\ContentEntityInterface $entity */ $entity = $route_match->getParameter($entity_type_id); $account = $this->currentUser(); $handler = $this->entityManager()->getHandler($entity_type_id, 'translation'); $manager = $this->manager; $entity_type = $entity->getEntityType(); // Start collecting the cacheability metadata, starting with the entity and // later merge in the access result cacheability metadata. $cacheability = CacheableMetadata::createFromObject($entity); $languages = $this->languageManager()->getLanguages(); $original = $entity->getUntranslated()->language()->getId(); $translations = $entity->getTranslationLanguages(); $field_ui = $this->moduleHandler()->moduleExists('field_ui') && $account->hasPermission('administer ' . $entity_type_id . ' fields'); $rows = array(); $show_source_column = FALSE; if ($this->languageManager()->isMultilingual()) { // Determine whether the current entity is translatable. $translatable = FALSE; foreach ($this->entityManager->getFieldDefinitions($entity_type_id, $entity->bundle()) as $instance) { if ($instance->isTranslatable()) { $translatable = TRUE; break; } } // Show source-language column if there are non-original source langcodes. $additional_source_langcodes = array_filter(array_keys($translations), function ($langcode) use($entity, $original, $manager) { $source = $manager->getTranslationMetadata($entity->getTranslation($langcode))->getSource(); return $source != $original && $source != LanguageInterface::LANGCODE_NOT_SPECIFIED; }); $show_source_column = !empty($additional_source_langcodes); foreach ($languages as $language) { $language_name = $language->getName(); $langcode = $language->getId(); $add_url = new Url("entity.{$entity_type_id}.content_translation_add", array('source' => $original, 'target' => $language->getId(), $entity_type_id => $entity->id()), array('language' => $language)); $edit_url = new Url("entity.{$entity_type_id}.content_translation_edit", array('language' => $language->getId(), $entity_type_id => $entity->id()), array('language' => $language)); $delete_url = new Url("entity.{$entity_type_id}.content_translation_delete", array('language' => $language->getId(), $entity_type_id => $entity->id()), array('language' => $language)); $operations = array('data' => array('#type' => 'operations', '#links' => array())); /** * allow translator to translate those content that languages are assign to him */ $user = \Drupal\user\Entity\User::load(\Drupal::currentUser()->id()); foreach ($user->get('field_language') as $lang) { $access_lang[] = $lang->value; } foreach ($user->get('roles') as $roles) { $current_user_roles[] = $roles->target_id; } $isaplied = 0; $nolink = 0; if (in_array('translator', $current_user_roles) && !in_array('administrator', $current_user_roles)) { $isaplied = 1; } if (!in_array($langcode, $access_lang)) { $nolink = 1; } /* * end here */ $links =& $operations['data']['#links']; if (array_key_exists($langcode, $translations)) { // Existing translation in the translation set: display status. $translation = $entity->getTranslation($langcode); $metadata = $manager->getTranslationMetadata($translation); $source = $metadata->getSource() ?: LanguageInterface::LANGCODE_NOT_SPECIFIED; $is_original = $langcode == $original; $label = $entity->getTranslation($langcode)->label(); $link = isset($links->links[$langcode]['url']) ? $links->links[$langcode] : array('url' => $entity->urlInfo()); if (!empty($link['url'])) { $link['url']->setOption('language', $language); $row_title = $this->l($label, $link['url']); } if (empty($link['url'])) { $row_title = $is_original ? $label : $this->t('n/a'); } // If the user is allowed to edit the entity we point the edit link to // the entity form, otherwise if we are not dealing with the original // language we point the link to the translation form. $update_access = $entity->access('update', NULL, TRUE); $translation_access = $handler->getTranslationAccess($entity, 'update'); $cacheability = $cacheability->merge(CacheableMetadata::createFromObject($update_access))->merge(CacheableMetadata::createFromObject($translation_access)); if ($update_access->isAllowed() && $entity_type->hasLinkTemplate('edit-form')) { $links['edit']['url'] = $entity->urlInfo('edit-form'); $links['edit']['language'] = $language; } elseif (!$is_original && $translation_access->isAllowed()) { $links['edit']['url'] = $edit_url; } if (isset($links['edit'])) { $links['edit']['title'] = $this->t('Edit'); } $status = array('data' => array('#type' => 'inline_template', '#template' => '<span class="status">{% if status %}{{ "Published"|t }}{% else %}{{ "Not published"|t }}{% endif %}</span>{% if outdated %} <span class="marker">{{ "outdated"|t }}</span>{% endif %}', '#context' => array('status' => $metadata->isPublished(), 'outdated' => $metadata->isOutdated()))); if ($is_original) { $language_name = $this->t('<strong>@language_name (Original language)</strong>', array('@language_name' => $language_name)); $source_name = $this->t('n/a'); } else { $source_name = isset($languages[$source]) ? $languages[$source]->getName() : $this->t('n/a'); $delete_access = $entity->access('delete', NULL, TRUE); $translation_access = $handler->getTranslationAccess($entity, 'delete'); $cacheability = $cacheability->merge(CacheableMetadata::createFromObject($delete_access))->merge(CacheableMetadata::createFromObject($translation_access)); if ($entity->access('delete') && $entity_type->hasLinkTemplate('delete-form')) { $links['delete'] = array('title' => $this->t('Delete'), 'url' => $entity->urlInfo('delete-form'), 'language' => $language); } elseif ($translation_access->isAllowed()) { $links['delete'] = array('title' => $this->t('Delete'), 'url' => $delete_url); } } /** * empty the translation link for language that are not assign to translator role users */ if ($isaplied == 1 && $nolink == 1) { $links = array(); } /** * end here */ } else { // No such translation in the set yet: help user to create it. $row_title = $source_name = $this->t('n/a'); $source = $entity->language()->getId(); $create_translation_access = $handler->getTranslationAccess($entity, 'create'); $cacheability = $cacheability->merge(CacheableMetadata::createFromObject($create_translation_access)); if ($source != $langcode && $create_translation_access->isAllowed()) { if ($translatable) { $links['add'] = array('title' => $this->t('Add'), 'url' => $add_url); } elseif ($field_ui) { $url = new Url('language.content_settings_page'); // Link directly to the fields tab to make it easier to find the // setting to enable translation on fields. $links['nofields'] = array('title' => $this->t('No translatable fields'), 'url' => $url); } } /** * empty the translation link for language that are not assign to translator role users */ if ($isaplied == 1 && $nolink == 1) { $links = array(); } /** * end here */ $status = $this->t('Not translated'); } if ($show_source_column) { $rows[] = array($language_name, $row_title, $source_name, $status, $operations); } else { $rows[] = array($language_name, $row_title, $status, $operations); } } } if ($show_source_column) { $header = array($this->t('Language'), $this->t('Translation'), $this->t('Source language'), $this->t('Status'), $this->t('Operations')); } else { $header = array($this->t('Language'), $this->t('Translation'), $this->t('Status'), $this->t('Operations')); } $build['#title'] = $this->t('Translations of %label', array('%label' => $entity->label())); // Add metadata to the build render array to let other modules know about // which entity this is. $build['#entity'] = $entity; $cacheability->addCacheTags($entity->getCacheTags())->applyTo($build); $build['content_translation_overview'] = array('#theme' => 'table', '#header' => $header, '#rows' => $rows); return $build; }
/** * Tests FormattedDateDiff. * * @covers \Drupal\Core\Datetime\FormattedDateDiff::toRenderable * @covers \Drupal\Core\Datetime\FormattedDateDiff::getString * @covers \Drupal\Core\Datetime\FormattedDateDiff::getCacheMaxAge */ public function testFormattedDateDiff() { $string = '10 minutes'; $max_age = 60; $object = new FormattedDateDiff($string, $max_age); // Test conversion to a render array. $expected = ['#markup' => $string, '#cache' => ['max-age' => $max_age]]; $this->assertArrayEquals($expected, $object->toRenderable()); // Test retrieving the formatted time difference string. $this->assertEquals($string, $object->getString()); // Test applying cacheability data to an existing build. $build = []; CacheableMetadata::createFromObject($object)->applyTo($build); $this->assertEquals($max_age, $build['#cache']['max-age']); // Test the BC layer. $this->assertSame($object->getCacheMaxAge(), $object->getMaxAge()); }