diff --git a/core/modules/help_topics/help_topics.info.yml b/core/modules/help_topics/help_topics.info.yml index fd909f33d1..57e822d327 100644 --- a/core/modules/help_topics/help_topics.info.yml +++ b/core/modules/help_topics/help_topics.info.yml @@ -1,9 +1,8 @@ name: Help Topics type: module -description: 'Displays and configures help topics' +description: 'Displays help topics provided by themes and modules.' core: 8.x package: Core (Experimental) -configure: entity.help_topic.collection version: VERSION dependencies: - drupal:help diff --git a/core/modules/help_topics/help_topics.module b/core/modules/help_topics/help_topics.module index a472f3ebb7..0b0916bf41 100644 --- a/core/modules/help_topics/help_topics.module +++ b/core/modules/help_topics/help_topics.module @@ -2,7 +2,7 @@ /** * @file - * Provides configurable help topics. + * Displays help topics provided by modules and themes. */ use Drupal\Core\Routing\RouteMatchInterface; @@ -22,13 +22,13 @@ function help_topics_help($route_name, RouteMatchInterface $route_match) { $output .= '
' . t('Viewing help topics') . '
'; $output .= '
' . t('The top-level help topics are listed on the main Help page. Links to other topics, including non-top-level help topics, can be found under the "Related" heading when viewing a topic page.', [':help_page' => Url::fromRoute('help.main')->toString()]) . '
'; $output .= '
' . t('Providing help topics') . '
'; - $output .= '
' . t('Modules and themes can provide help topics as YAML-file-based plugins in a project sub-directory called help_topics. Any file in a module or theme\'s help_topics directory with the suffix *.help_topic.yml will be discovered by the Help Topic module. Plugin-based help topics provided by modules and themes will automatically be updated when a module or theme is updated. It is advisable not to edit the YAML files of module- or theme-provided topics, to make updates easier. Use the plugins in core/modules/help_topics/help_topics as a guide when writing and formatting a help topic plugin for your theme or module.') . '
'; + $output .= '
' . t("Modules and themes can provide help topics as YAML-file-based plugins in a project sub-directory called help_topics. Any file in a module or theme's help_topics directory with the suffix *.help_topic.yml will be discovered by the Help Topic module. Plugin-based help topics provided by modules and themes will automatically be updated when a module or theme is updated. It is advisable not to edit the YAML files of module- or theme-provided topics, to make updates easier. Use the plugins in core/modules/help_topics/help_topics as a guide when writing and formatting a help topic plugin for your theme or module.") . '
'; $output .= ''; return ['#markup' => $output]; case 'help_topics.help_topic': return '

' . t('See the Help page for more topics.', [ - ':help_page' => Url::fromRoute('help.main')->toString() + ':help_page' => Url::fromRoute('help.main')->toString(), ]) . '

'; } } diff --git a/core/modules/help_topics/src/Controller/AutocompleteController.php b/core/modules/help_topics/src/Controller/AutocompleteController.php deleted file mode 100644 index 2ffcf2ace7..0000000000 --- a/core/modules/help_topics/src/Controller/AutocompleteController.php +++ /dev/null @@ -1,244 +0,0 @@ -pluginManager = $plugin_manager; - $this->modules = $modules; - $this->moduleHandler = $module_handler; - $this->themeHandler = $theme_handler; - } - - /** - * {@inheritdoc} - */ - public static function create(ContainerInterface $container) { - return new static( - $container->get('plugin.manager.help_topic'), - $container->get('extension.list.module'), - $container->get('module_handler'), - $container->get('theme_handler') - ); - } - - /** - * Retrieves suggestions for help topic autocomplete. - * - * The autocomplete suggestions search for matches by topic title and machine - * name, and are returned in a JSON response for use in an edit form field. - * - * @param \Symfony\Component\HttpFoundation\Request $request - * The request object. - * - * @return \Symfony\Component\HttpFoundation\JsonResponse - * A JSON response containing the autocomplete suggestions. - */ - public function topicAutocomplete(Request $request) { - $matches = []; - $count = 0; - if ($input = $this->getUserInput($request)) { - $topics = $this->pluginManager->findMatches($input); - if (!empty($topics)) { - foreach ($topics as $title => $id) { - $matches[] = $this->getMatch($id, $title); - $count++; - if ($count >= 10) { - break; - } - } - } - } - - return new JsonResponse($matches); - } - - /** - * Retrieves suggestions for module autocomplete. - * - * The autocomplete suggestions search for matches by module displayed and - * machine name, and they are returned in a JSON response for use in an edit - * form field. - * - * @param \Symfony\Component\HttpFoundation\Request $request - * The request object. - * - * @return \Symfony\Component\HttpFoundation\JsonResponse - * A JSON response containing the autocomplete suggestions. - */ - public function moduleAutocomplete(Request $request) { - $matches = []; - if ($input = $this->getUserInput($request)) { - // Return only first 10 matches. - $limit = 10; - foreach ($this->getActiveModules() as $name => $label) { - // Add as a match if the typed text matches machine name or displayed - // name. - if (stripos($name, $input) !== FALSE || stripos($label, $input) !== FALSE) { - $matches[] = $this->getMatch($name, $label); - if (!--$limit) { - break; - } - } - } - } - return new JsonResponse($matches); - } - - /** - * Retrieves suggestions for theme autocomplete. - * - * The autocomplete suggestions search for matches by theme displayed and - * machine name, and they are returned in a JSON response for use in an edit - * form field. - * - * @param \Symfony\Component\HttpFoundation\Request $request - * The request object. - * - * @return \Symfony\Component\HttpFoundation\JsonResponse - * A JSON response containing the autocomplete suggestions. - */ - public function themeAutocomplete(Request $request) { - $matches = []; - if ($input = $this->getUserInput($request)) { - // Return only first 10 matches. - $limit = 10; - foreach ($this->getThemes() as $name => $label) { - // Add as a match if the typed text matches machine name or displayed - // name. - if (strpos($name, $input) !== FALSE || strpos($label, $input) !== FALSE) { - $matches[] = $this->getMatch($name, $label); - if (!--$limit) { - break; - } - } - } - } - return new JsonResponse($matches); - } - - /** - * Returns the last typed tag from user input passed to autocomplete. - * - * @param \Symfony\Component\HttpFoundation\Request $request - * The request object. - * - * @return string - * The last tag. - */ - protected function getUserInput(Request $request) { - $input = $request->query->get('q'); - if (!$input) { - return ''; - } - $input = Tags::explode($input); - return mb_strtolower(array_pop($input)); - } - - /** - * Builds the structure to display in an autocomplete dropdown. - * - * @param string $value - * Machine name to display and use as the value. - * @param string $label - * Human-readable name to display. - * - * @return array - * An array of matched labels, in the format required by the Ajax - * autocomplete API (array('value' => $value, 'label' => $label)). - */ - protected function getMatch($value, $label) { - return [ - 'value' => $value, - 'label' => new HtmlEscapedText("$label ($value)"), - ]; - } - - /** - * Returns the list of active modules' names. - * - * @return array - * An associative array of active modules' human names keyed by module name. - */ - protected function getActiveModules() { - $all = $this->modules->getAllAvailableInfo(); - $installed = array_keys($this->moduleHandler->getModuleList()); - $modules = []; - foreach ($installed as $extension) { - $modules[$extension] = $all[$extension]['name']; - } - // Modules are sorted by their weight so sort them by names. - asort($modules); - return $modules; - } - - /** - * Returns the list of installed themes' names. - * - * @return array - * An associative array of theme names keyed by machine name of the theme. - */ - protected function getThemes() { - // There is not an obvious way to query installed themes. So make - // a list of all themes, and filter it down to ones that match. - $installed = $this->themeHandler->listInfo(); - $themes = []; - foreach ($installed as $name => $extension) { - $themes[$name] = $extension->info['name']; - } - // Themes are sorted by file system discovery so sort them by name. - asort($themes); - return $themes; - } - -} diff --git a/core/modules/help_topics/src/Controller/HelpTopicPluginController.php b/core/modules/help_topics/src/Controller/HelpTopicPluginController.php index 39eae8f0be..8add59ebce 100644 --- a/core/modules/help_topics/src/Controller/HelpTopicPluginController.php +++ b/core/modules/help_topics/src/Controller/HelpTopicPluginController.php @@ -66,8 +66,9 @@ public static function create(ContainerInterface $container) { /** * Displays a help topic page. * - * @param $id - * The plugin id. Maps to the {id} placeholder in the help_topics.help_topic route. + * @param string $id + * The plugin id. Maps to the {id} placeholder + * in the help_topics.help_topic route. * * @return array * A render array with the contents of a help topic page. @@ -75,7 +76,7 @@ public static function create(ContainerInterface $container) { public function viewHelpTopic($id) { $build = []; - /** @var \Drupal\help_topics\Plugin\HelpTopic\HelpTopicPluginInterface $help_topic */ + /* @var \Drupal\help_topics\Plugin\HelpTopic\HelpTopicPluginInterface $help_topic */ $help_topic = $this->helpTopicPluginManager->createInstance($id); $body = $help_topic->getBody(); diff --git a/core/modules/help_topics/src/HtmlChunker.php b/core/modules/help_topics/src/HtmlChunker.php index e62419a554..e87ee010ad 100644 --- a/core/modules/help_topics/src/HtmlChunker.php +++ b/core/modules/help_topics/src/HtmlChunker.php @@ -25,7 +25,7 @@ class HtmlChunker { * * @see \Drupal\help_topics\HtmlChunker::joinChunks() */ - public static function chunkHTML($html) { + public static function chunkHtml($html) { $dom = Html::load($html); if (!$dom) { return FALSE; @@ -51,9 +51,9 @@ public static function chunkHTML($html) { * @return string * HTML string containing all of the chunks. * - * @see \Drupal\help_topics\HtmlChunker::chunkHTML() + * @see \Drupal\help_topics\HtmlChunker::chunkHtml() */ - public static function joinChunks($chunks) { + public static function joinChunks(array $chunks) { $text = ''; foreach ($chunks as $chunk) { $text .= $chunk['prefix_tags'] . $chunk['text'] . $chunk['suffix_tags']; diff --git a/core/modules/help_topics/src/Plugin/HelpSection/HelpTopicSection.php b/core/modules/help_topics/src/Plugin/HelpSection/HelpTopicSection.php index f4dab75fdf..8dfabf577e 100644 --- a/core/modules/help_topics/src/Plugin/HelpSection/HelpTopicSection.php +++ b/core/modules/help_topics/src/Plugin/HelpSection/HelpTopicSection.php @@ -13,7 +13,7 @@ * @HelpSection( * id = "help_topics", * title = @Translation("Topics"), - * description = @Translation("Topics can be provided by modules, themes, installation profiles, or site administrators, and they may be organized hierarchically. Top-level help topics configured on your site:"), + * description = @Translation("Topics can be provided by modules or themes. Top-level help topics configured on your site:"), * permission = "view help topics" * ) */ diff --git a/core/modules/help_topics/src/Plugin/HelpTopic/HelpTopicPluginManager.php b/core/modules/help_topics/src/Plugin/HelpTopic/HelpTopicPluginManager.php index 931102be38..f5e4720d88 100644 --- a/core/modules/help_topics/src/Plugin/HelpTopic/HelpTopicPluginManager.php +++ b/core/modules/help_topics/src/Plugin/HelpTopic/HelpTopicPluginManager.php @@ -15,9 +15,6 @@ * Modules and themes can provide help topics in YAML files called * name_of_topic.help_topic.yml inside the module or theme sub-directory * help_topics. - * - * The format of this file is the same as for the help topic entity. See - * config/schema/help_topics.schema.yml */ class HelpTopicPluginManager extends DefaultPluginManager implements HelpTopicPluginManagerInterface { @@ -27,9 +24,9 @@ class HelpTopicPluginManager extends DefaultPluginManager implements HelpTopicPl * @var array */ protected $defaults = [ - // The plugin ID. Set by the plugin system based on the top-level YAML key + // The plugin ID. Set by the plugin system based on the top-level YAML key. 'id' => '', - // The title of the help topic plugin + // The title of the help topic plugin. 'label' => '', // Whether or not the topic should appear on the help topics list. 'top_level' => '', @@ -95,10 +92,10 @@ public function __construct(ModuleHandlerInterface $module_handler, ThemeHandler * * @param array $definition * The definition to be processed and modified by reference. - * @param $plugin_id + * @param string $plugin_id * The ID of the plugin this definition is being used for. */ - public function processDefinition(&$definition, $plugin_id) { + public function processDefinition(array &$definition, $plugin_id) { $definition = NestedArray::mergeDeep($this->defaults, $definition); $definition['id'] = $plugin_id; } @@ -141,7 +138,7 @@ protected function getFactory() { */ public function getDefinitions() { // Since this function is called rarely, instantiate the discovery here. - // This finds all the help_topic plugins in theme and module directories + // This finds all the help_topic plugins in theme and module directories. $definitions = $this->getDiscovery()->getDefinitions(); foreach ($definitions as $plugin_id => &$definition) { @@ -171,7 +168,7 @@ public function getTopLevelTopics() { foreach ($this->getDefinitions() as $definition) { if ($definition['top_level']) { - /** @var \Drupal\help_topics\Plugin\HelpTopic\HelpTopicPluginInterface $topic **/ + /* @var \Drupal\help_topics\Plugin\HelpTopic\HelpTopicPluginInterface $topic */ $topic = $this->createInstance($definition['id']); $label = (string) $topic->getLabel(); diff --git a/core/modules/help_topics/tests/src/Functional/HelpTopicTest.php b/core/modules/help_topics/tests/src/Functional/HelpTopicTest.php index 69cf5357aa..94969a2a31 100644 --- a/core/modules/help_topics/tests/src/Functional/HelpTopicTest.php +++ b/core/modules/help_topics/tests/src/Functional/HelpTopicTest.php @@ -85,12 +85,12 @@ public function testHelp() { $this->drupalGet('admin/help'); $session = $this->assertSession(); $session->responseContains('

Topics

'); - $session->pageTextContains('Topics can be provided by modules, themes'); + $session->pageTextContains('Topics can be provided by modules or themes'); // Verify the cache tag for the cache context for user permissions. $this->assertCacheContext('user.permissions'); - // Verify links for for configurable topics, and order. + // Verify links for for help topics and order. $page_text = $this->getTextContent(); $start = strpos($page_text, 'Topics can be provided'); $pos = $start; @@ -124,7 +124,7 @@ public function testHelp() { * for users who don't even have 'view help topics' permission. */ protected function verifyHelp($response = 200, $check_tags = TRUE) { - // Verify access to configurable help topic pages. + // Verify access to help topic pages. foreach ($this->getTopicList() as $topic => $info) { // View help topic page. $this->drupalGet('admin/help/topic/' . $topic); @@ -190,15 +190,12 @@ protected function getTopicList() { 'help_test' => [ 'name' => 'ABC Help Test module', 'cache_tags' => [ - 'help_test', - 'help_test_linked', 'config:filter.format.help', ], ], 'help_topic_writing' => [ 'name' => 'Writing good help', 'cache_tags' => [ - 'help_topic_writing', 'config:filter.format.help', ], ], diff --git a/core/modules/help_topics/tests/src/Kernel/HelpTopicTokensTest.php b/core/modules/help_topics/tests/src/Kernel/HelpTopicTokensTest.php new file mode 100644 index 0000000000..695d3422e4 --- /dev/null +++ b/core/modules/help_topics/tests/src/Kernel/HelpTopicTokensTest.php @@ -0,0 +1,108 @@ +installSchema('system', ['router', 'sequences']); + + // Set up as multi-lingual with English and Spanish, so that we can + // test for cache metadata from URL generation. + $this->installConfig(['help_topics', 'help_topics_test', 'language']); + $this->installEntitySchema('configurable_language'); + ConfigurableLanguage::create(['id' => 'es'])->save(); + $this + ->config('language.negotiation') + ->set('url.prefixes', [ + 'en' => 'en', + 'es' => 'es', + ]) + ->save(); + \Drupal::service('kernel')->rebuildContainer(); + \Drupal::service('router.builder')->rebuild(); + } + + /** + * Tests that help topic tokens work. + */ + public function testHelpTopicTokens() { + // Verify a URL token for a help topic that is a plugin. + $bubbleable_metadata = new BubbleableMetadata(); + $text = 'This should Link to help topic'; + $replaced = \Drupal::token()->replace($text, [], [], $bubbleable_metadata); + $this->assertTrue(strpos($replaced, 'getCacheContexts(); + $this->assertTrue(in_array('languages:language_url', $contexts, 'Language negotiation cache context was added')); + + // Verify correct URL if we tell the Token system to use Spanish. + $bubbleable_metadata = new BubbleableMetadata(); + $replaced = \Drupal::token()->replace($text, [], ['langcode' => 'es'], $bubbleable_metadata); + $spanish = \Drupal::languageManager()->getLanguage('es'); + $this->assertTrue(strpos($replaced, 'replace($text); + $this->assertTrue(strpos($replaced, '[help_topic:url:nonexistent]') !== FALSE, 'Nonexistent help topic did not get replaced'); + + // Check for cache metadata. It should have a cache context for language + // negotiation and a tag for the text format. + $tags = $bubbleable_metadata->getCacheTags(); + $contexts = $bubbleable_metadata->getCacheContexts(); + $this->assertTrue(in_array('languages:language_url', $contexts, 'Language negotiation cache context was added')); + } + + /** + * Tests that route tokens work. + */ + public function testRouteTokens() { + // Verify correct URL for a system route. + $bubbleable_metadata = new BubbleableMetadata(); + $text = 'This should Link to admin'; + $replaced = \Drupal::token()->replace($text, [], [], $bubbleable_metadata); + $this->assertTrue(strpos($replaced, 'getCacheContexts(); + $this->assertTrue(in_array('languages:language_url', $contexts, 'Language negotiation cache context was added')); + + // Verify there is no replacement for an invalid route. + $text = 'This should Not link to admin'; + $replaced = \Drupal::token()->replace($text); + $this->assertTrue(strpos($replaced, '[route:url:system.nonexistent]') !== FALSE, 'Nonexistent route was not replaced'); + } + +}