diff --git a/src/Element/TextSections.php b/src/Element/TextSections.php index 4a60a20..0cca879 100644 --- a/src/Element/TextSections.php +++ b/src/Element/TextSections.php @@ -3,6 +3,7 @@ namespace Drupal\config_help\Element; use Drupal\Core\Render\Element\RenderElement; +use Drupal\config_help\TextSectionPluginCollection; /** * Provides a text sections render element. @@ -62,61 +63,13 @@ class TextSections extends RenderElement { * The form element. */ public static function preRenderSections(array $element) { - // Iterate through the provided sections, adding each as a new render - // array child to $element, or as part of a grouped render array. - $sections = $element['#sections']; + /** @var \Drupal\config_help\TextSectionPluginCollection */ + $collection = new TextSectionPluginCollection(self::sectionManager(), []); + $collection->addFromRenderArray($element); + unset($element['#sections']); unset($element['#type']); - $manager = self::sectionManager(); - $element['text_sections'] = []; - - $previous_type = ''; - $group_build = []; - - /** @var \Drupal\Core\Render\RendererInterface $renderer */ - $renderer = \Drupal::service('renderer'); - foreach ($sections as $section) { - /** @var \Drupal\config_help\TextSectionPluginInterface $plugin */ - $plugin = $manager->createInstance($section['#section_type']); - if (!$plugin) { - // Do not attempt to render unknown types. Just skip them. - continue; - } - - // Set up the inner build. - $renderer->addCacheableDependency($element, $plugin); - $plugin->setText($section['#text']); - - // Check for grouping. - $this_group_with = $plugin->groupWith(); - - if (!in_array($previous_type, $this_group_with) && !empty($group_build)) { - // This section should not be grouped with the previous one, so add the - // previous grouping to the element and reset the in-progress group. - $element['text_sections'][] = $group_build; - $group_build = []; - } - - if (!empty($this_group_with)) { - // This is a grouped section, either for a new group or a compatible - // group. Add this to the group, or initialize a new group. - $group_build = $plugin->addToGroup($group_build); - } - else { - // It is not grouped, so just add it to the element. - $element['text_sections'][] = $plugin->getInnerBuild(); - } - - // Save this type for the next grouping decision. - $previous_type = $section['#section_type']; - } - - // Add the last in-progress group to the element if there is one. - if (!empty($group_build)) { - $element['text_sections'][] = $group_build; - } - - return $element; + return $collection->preRenderCollection(self::renderer(), $element); } /** @@ -129,4 +82,14 @@ class TextSections extends RenderElement { return \Drupal::service('plugin.manager.text_section'); } + /** + * Wraps the renderer service. + * + * @return \Drupal\Core\Render\Renderer + * The renderer. + */ + protected static function renderer() { + return \Drupal::service('renderer'); + } + } diff --git a/src/HelpViewBuilder.php b/src/HelpViewBuilder.php index aac2697..a2b4638 100644 --- a/src/HelpViewBuilder.php +++ b/src/HelpViewBuilder.php @@ -80,15 +80,18 @@ class HelpViewBuilder extends EntityViewBuilder { foreach ($entities as $entity_id => $help_topic) { // Get the cache information and other build defaults. $build = $this->getBuildDefaults($help_topic, $view_mode); - $build['#langcode'] = $langcode; $build['#title'] = $help_topic->label(); - $body = $help_topic->getBody(); - $build['#body'] = $this->processBody($body); + // Add in the body. + $collections = $help_topic->getPluginCollections(); + /** @var \Drupal\config_help\TextSectionPluginCollection */ + $body_collection = $collections['body']; + $build['#body'] = $body_collection->buildRenderArray([$this, 'processBodyItem']); - // The list should include topics this entity lists as related, plus - // topics that have said "Add me to this topic's related list". + // Figure out which topics to list as related, including topics this + // entity lists as related, plus topics that have said "Add me to this + // topic's related list" using the list on field. $related = $help_topic->getRelated() + $this->queryFactory->get('help_topic') ->condition('list_on.*', $help_topic->id()) @@ -128,39 +131,24 @@ class HelpViewBuilder extends EntityViewBuilder { } /** - * Processes the raw body of a topic into a render array. + * Processes a piece of text from the body. * - * @param array $body - * Body value from the config help entity. See - * \Drupal\config_help\HelpTopicInterface::getBody() for details. + * @param array $item + * Array containing the 'value' and 'format' elements from a single + * paragraph-sized piece of the body. * * @return array - * Render array for the body of the help topic. + * Render array section for this piece of the body, with tokens replaced. */ - protected function processBody(array $body) { - if (empty($body)) { - return []; - } - - $sections = []; - foreach ($body as $item) { - $bubbleable_metadata = new BubbleableMetadata(); - $section = [ - '#section_type' => $item['id'], - '#text' => [ - '#type' => 'processed_text', - '#text' => $this->token->replace($item['text']['value'], [], [], $bubbleable_metadata), - '#format' => $item['text']['format'], - ], - ]; - $bubbleable_metadata->applyTo($section); - $sections[] = $section; - } - - return [ - '#type' => 'text_sections', - '#sections' => $sections, + public function processBodyItem(array $item) { + $bubbleable_metadata = new BubbleableMetadata(); + $section = [ + '#type' => 'processed_text', + '#text' => $this->token->replace($item['value'], [], [], $bubbleable_metadata), + '#format' => $item['format'], ]; + $bubbleable_metadata->applyTo($section); + return $section; } } diff --git a/src/TextSectionPluginCollection.php b/src/TextSectionPluginCollection.php index 73ac5a0..4ad83a0 100644 --- a/src/TextSectionPluginCollection.php +++ b/src/TextSectionPluginCollection.php @@ -4,9 +4,127 @@ namespace Drupal\config_help; use Drupal\Component\Plugin\PluginManagerInterface; use Drupal\Core\Plugin\DefaultLazyPluginCollection; +use Drupal\Core\Render\Renderer; /** * Provides a plugin collection class for text sections on a help topic. */ class TextSectionPluginCollection extends DefaultLazyPluginCollection { + + /** + * Creates a text_sections render array from the collection. + * + * @param callable $callback + * (optional) Function to apply to the 'text' element of the configuration + * saved for each plugin in the collection. + * + * @return array + * Render array of type 'text_sections', containing the appropriate text + * sections. + * + * @see \Drupal\config_help\TextSectionPluginCollection::addFromRenderArray() + */ + public function buildRenderArray($callback = NULL) { + $build = [ + '#type' => 'text_sections', + '#sections' => [], + ]; + + $configuration = $this->getConfiguration(); + if (!$configuration) { + return $build; + } + + foreach ($configuration as $key => $item) { + $section = [ + '#section_type' => $item['id'], + '#text' => $item['text'], + ]; + if ($callback) { + $section['#text'] = call_user_func($callback, $section['#text']); + } + $build['#sections'][] = $section; + } + + return $build; + } + + /** + * Adds plugins to the collection from a text_sections render array. + * + * @param array $element + * Render array element of #type 'text_sections' to add plugins from. + * + * @return $this + * + * @see \Drupal\config_help\TextSectionPluginCollection::buildRenderArray() + */ + public function addFromRenderArray(array $element) { + $sections = $element['#sections']; + foreach ($sections as $index => $section) { + $this->addInstanceId($index, [ + 'id' => $section['#section_type'], + 'text' => $section['#text'], + ]); + } + + return $this; + } + + /** + * Pre-renders the current collection. + * + * @param \Drupal\Core\Render\Renderer $renderer + * The renderer service to use. + * @param array $element + * The render array to add information to. + * + * @return array + * Render array: starts with $element, and adds the formatted text from the + * plugins. + */ + public function preRenderCollection(Renderer $renderer, array $element) { + $previous_type = ''; + $group_build = []; + $element['text_sections'] = []; + + $all_configuration = $this->getConfiguration(); + foreach ($all_configuration as $id => $config) { + /** @var \Drupal\config_help\TextSectionPluginInterface $plugin */ + $plugin = $this->get($id); + $plugin->setText($config['text']); + $renderer->addCacheableDependency($element, $plugin); + + // Check for grouping. + $this_group_with = $plugin->groupWith(); + + if (!in_array($previous_type, $this_group_with) && !empty($group_build)) { + // This section should not be grouped with the previous one, so add the + // previous grouping to the element and reset the in-progress group. + $element['text_sections'][] = $group_build; + $group_build = []; + } + + if (!empty($this_group_with)) { + // This is a grouped section, either for a new group or a compatible + // group. Add this to the group, or initialize a new group. + $group_build = $plugin->addToGroup($group_build); + } + else { + // It is not grouped, so just add it to the element. + $element['text_sections'][] = $plugin->getInnerBuild(); + } + + // Save this type for the next grouping decision. + $previous_type = $plugin->getPluginId(); + } + + // Add the last in-progress group to the element if there is one. + if (!empty($group_build)) { + $element['text_sections'][] = $group_build; + } + + return $element; + } + }