Problem/Motivation

This issue is spun off from the patch on #2351991: Add a config entity for a configurable, topic-based help system, which in turn became a sandbox module:
https://www.drupal.org/sandbox/jhodgdon/2369943

One part of that issue was an overhaul of the admin/help page, to make it more flexible. The idea is that there are actually multiple Core and Contrib modules that provide "help" (broadly defined as "useful information that is helpful for people new to Drupal or some of its systems" or something like that). It would be useful, therefore, if the Help page would list all available help, instead of what it is doing now, which is just listing module overview pages provided by hook_help() in installed modules.

If the Tour module participated in this, it would also take care of providing #2205801: Create Available Tours block, but not as a block as proposed in that issue.

Proposed resolution

a) Add a plugin that allows modules to provide sections to the help page.

b) Have both the core Help module and the core Tour module implement this plugin, so that the Help page will list both hook_help() module overview pages, and available tours.

c) As a side effect of this, update how the markup is provided for the admin/help page, so that it is all in the theme layer instead of being hard-coded in the HelpController class. See also #2337317: Replace help page layout CSS with reuseable layout classes, which went part-way down this path but didn't really do enough.

Remaining tasks

Make patch and commit.

User interface changes

The Help page will be more flexible, and show more help, including Tours and potentially help from contrib modules that choose to provide help page sections (Advanced Help could do so, for instance)

Before

After

API changes

New plugin type is added.

The markup and text of the admin/help page changes slightly.

Markup of the admin/help page is moved into the theme layer instead of being hard-coded in HelpController.

Data model changes

None.

CommentFileSizeAuthor
#81 interdiff.txt2.53 KBjhodgdon
#81 2661200-81.patch36.28 KBjhodgdon
#77 interdiff.txt1.99 KBstar-szr
#77 make_admin_help_page-2661200-77.patch34.54 KBstar-szr
#65 2661200_65.patch34.62 KBmile23
#58 interdiff-51-58.txt5.53 KBjhodgdon
#58 2661200-58.patch34.62 KBjhodgdon
#52 interdiff.txt10.62 KBwim leers
#52 interdiff-url-changes.txt6.36 KBwim leers
#52 interdiff-relevant-changes.txt4.35 KBwim leers
#52 2661200-52.patch41.84 KBwim leers
#51 interdiff.txt2.08 KBwim leers
#51 2661200-51.patch35.02 KBwim leers
#48 2661200-48.patch33.93 KBjhodgdon
#48 interdiff.txt5.06 KBjhodgdon
#46 interdiff.txt1.3 KBjhodgdon
#46 2661200-46.patch34.41 KBjhodgdon
#43 2661200-43.patch34.37 KBjhodgdon
#43 interdiff.txt1010 bytesjhodgdon
#40 interdiff.txt9.39 KBjhodgdon
#40 2661200-39.patch34.23 KBjhodgdon
#38 2661200-38.patch33.47 KBjhodgdon
#31 Screenshot 2016-02-17 07.56.23.png35.32 KBlarowlan
#27 2661200-27b.patch33.46 KBjhodgdon
#27 interdiff.txt4.03 KBjhodgdon
#25 interdiff.txt1.33 KBjhodgdon
#25 2661200-25.patch33.39 KBjhodgdon
#22 2661200-22.patch33.58 KBjhodgdon
#21 interdiff.txt8.78 KBjhodgdon
#20 2661200-20.patch32.24 KBjhodgdon
#20 2661200-20.patch32.24 KBjhodgdon
#18 interdiff.txt1.95 KBjhodgdon
#18 2661200-18.patch32.95 KBjhodgdon
#17 10824282-screenshot-16.png59.53 KBvijaycs85
#16 help-index.png285.47 KBvijaycs85
#15 interdiff.txt4.36 KBjhodgdon
#15 2661200-16.patch32.48 KBjhodgdon
#12 interdiff.txt26.24 KBjhodgdon
#12 2661200-12.patch32.37 KBjhodgdon
#7 2661200-7.patch22.87 KBjhodgdon
#5 interdiff.txt1.6 KBjhodgdon
#5 2661200-5.patch22.18 KBjhodgdon
#3 oldhelppage.png96.78 KBjhodgdon
#3 newhelppage.png116.54 KBjhodgdon
#3 2661200.patch21.51 KBjhodgdon

Comments

jhodgdon created an issue. See original summary.

jhodgdon’s picture

And by the way, I'm working on a patch now...

jhodgdon’s picture

Status: Active » Needs review
StatusFileSize
new21.51 KB
new116.54 KB
new96.78 KB

Here's an initial patch, with tests, docs, etc.

Also here are screen shots of the new/old help pages (they are kind of large images so just linking not embedding in this comment, hope that is OK). Differences to note:
- Slight change in wording for "For more information" sentence in Getting Started.
- "Help topics" section on old becomes "Module overviews" (and description text line also changes accordingly). Pay no attention to the differences in the list of topics here though... The screen shots came from two different sites, and I had more modules enabled on the "New" site than on "Old". Sorry about that.
- New page also lists tours

Screen shots -- new:

Screen shot of new help page

Old:
Screen shot of old help page

Status: Needs review » Needs work

The last submitted patch, 3: 2661200.patch, failed testing.

jhodgdon’s picture

Status: Needs work » Needs review
StatusFileSize
new22.18 KB
new1.6 KB

Fixing test failures. I had to add the template to Stable, fix the NoHelp test (it was looking for the old test on the admin/help page) and the help.module file (it was skipping modules that didn't provide a main help topic, which violated the HelpTest test).

Note that the interdiff file doesn't include the added template in Stable -- it only has the other two changes. Hope that is OK.

Tests pass locally now; should be OK to review.

Status: Needs review » Needs work

The last submitted patch, 5: 2661200-5.patch, failed testing.

jhodgdon’s picture

StatusFileSize
new22.87 KB

Dang, sorry, uploaded wrong patch file there, without my updates from the interdiff (forgot to git add -u them, oops).

jhodgdon’s picture

Status: Needs work » Needs review
jhodgdon’s picture

Tests pass this time. ;)

larowlan’s picture

I love this idea - great work @jhodgdon -
some observations:

  1. +++ b/core/modules/help/help.api.php
    @@ -56,5 +56,65 @@ function hook_help($route_name, \Drupal\Core\Routing\RouteMatchInterface $route_
    +function hook_help_section_info() {
    

    I was of the understanding that we were trying to avoid adding new hooks for discovery - we worked hard in Drupal 8 cycle to remove most of them in favour of plugins. Is that still the case? Moving to a plugin would make this more discoverable and self-documenting as we could provide an interface:

    interface HelpSectionInterface {
      public function getHeader();
      public function getDescription();
      public function getPermission();
      public function buildTopics();
    }
    

    and possibly a base class for the permission bit, which would be pretty common.

  2. +++ b/core/modules/help/help.module
    @@ -60,3 +76,44 @@ function help_block_view_help_block_alter(array &$build, BlockPluginInterface $b
    +function help_list_hook_help_topics() {
    +  $topics = [];
    +  $route_match = \Drupal::routeMatch();
    +  $module_info = system_rebuild_module_data();
    +
    +  foreach (\Drupal::moduleHandler()->getImplementations('help') as $module) {
    

    This could be a service, with injected module handler, link generator and route match, which would allow unit-testing

  3. +++ b/core/modules/help/src/Controller/HelpController.php
    @@ -45,57 +45,86 @@ public static function create(ContainerInterface $container) {
    +  protected function fourColumnList($items) {
    

    This is nice

  4. +++ b/core/modules/tour/tour.module
    @@ -111,3 +112,68 @@ function tour_tour_insert($entity) {
    +  $tags = \Drupal::entityTypeManager()->getDefinition('tour')->getListCacheTags();
    ...
    +  $tour_storage = \Drupal::entityTypeManager()->getStorage('tour');
    

    If these were plugins, we could use ContainerFactoryPluginInterface and inject the tour definition and the storage handler

  5. +++ b/core/modules/tour/tour.module
    @@ -111,3 +112,68 @@ function tour_tour_insert($entity) {
    +        $topics[$id] = Link::createFromRoute($title, $route['route_name'], $params)->toString();
    

    We need to check access here, not all routes will be visible to every user that has access to help pages, and hence not all tours will be available.

    We need to add test coverage for that scenario too.

  6. +++ b/core/modules/tour/tour.module
    @@ -111,3 +112,68 @@ function tour_tour_insert($entity) {
    +        // If there was an exception, just try the next route.
    

    In what circumstances would this occur? Could we document them?

jhodgdon’s picture

Status: Needs review » Needs work

Thanks for the review!

In regards to item 6, I did document in there I think that the problem is that some routes require parameters. So the try/catch is to find a route that doesn't. For instance, you might have a tour on the foo/add and foo/{foo}/edit pages. The edit page would exception out (it has a parameter) and the add page would work without parameters. Anyway I'll fix the comments so this is clearer.

Good idea on the plugin vs. hook. I'll work that up...

jhodgdon’s picture

Status: Needs work » Needs review
StatusFileSize
new32.37 KB
new26.24 KB

OK! I have implemented a plugin system instead of hook for the help page sections. I think all the review comments in #10 have been addressed (most were related to that). One note: what I decided to do for the Tours section permissions check was go ahead and list them but not make links if the user doesn't have permission to visit that page. This functionality is also tested in TourHelpPageTest. Note that all modules are shown in the Module Overview section too, even if the user doesn't have admin permissions on that module, so it is kind of parallel to list the tours but I agree that providing 403 links to the admin pages with the tours on them is probably not useful.

The screen shots are exactly the same as in #3. Only the underlying functionality has changed (extensively). Tests pass locally; let's see what the bot says.

I've included an interdiff file, although since it's nearly as large as the patch (almost everything changed), I am pretty sure it is not very useful. I recommend looking at the patch file instead, but whatever. ;)

jhodgdon’s picture

Tests passed; let round 2 of reviews begin! (thanks in advance!!)

larowlan’s picture

Thank you @jhodgdon for considering my input - such a quick turnaround too!
Here are some observations, but I think this is very close.

  1. +++ b/core/modules/help/src/Annotation/HelpSection.php
    @@ -0,0 +1,64 @@
    +  /**
    +   * The permission needed to view the help section.
    +   *
    +   * Set to '' if there is no special permission needed beyond the generic
    +   * 'access administration pages' permission needed to see the admin/help page
    +   * itself.
    +   *
    +   * @var string
    +   */
    +  public $permission;
    
    +++ b/core/modules/help/src/Plugin/HelpSection/HookHelpSection.php
    @@ -0,0 +1,96 @@
    + *   permission = ""
    

    I think you can set a default here

    public $permission = 'access administration pages';
    

    And then those that don't need anything more can simply omit this parameter in their annotation. That's how doctrine annotations work outside Drupal, not sure if there is some Drupal specific stuff that breaks that, but in general doctrine/annotations support that

  2. +++ b/core/modules/help/src/Controller/HelpController.php
    @@ -40,62 +51,91 @@ public function __construct(RouteMatchInterface $route_match) {
    +        '#links' => $this->t('There is currently nothing in this section'),
    ...
    +        $this_output['#links'] = $this->fourColumnList($links);
    

    Sometimes #links is an array, sometimes it is a string. Given we pass this into the alter hook (hook_help_section_info_alter) - should we use a render array for the 'There is currently nothing...' too, so the format of #links is consistently an array?

  3. +++ b/core/modules/help/src/HelpSectionPluginInterface.php
    @@ -0,0 +1,43 @@
    +   *   help page section. Empty array if there isn't any. See the Caching
    

    'Empty array if there isn't any' sounds a bit abrupt. Suggest 'If the returned topics don't require cache metadata, return an empty array' or similar? (docs isn't my strongest point, so defer to your expertise)

  4. +++ b/core/modules/help/src/Plugin/HelpSection/HookHelpSection.php
    @@ -0,0 +1,96 @@
    +    // If a module adds or removes hook_help() or gets installed/uninstalled,
    +    // hopefully the page cache would get cleared anyway.
    

    Do we normally include things like that in documentation 'hopefully' :). I can confirm that ModuleInstaller::uninstall flushes all cache bins -

    $this->moduleHandler->invokeAll('cache_flush');
        foreach (Cache::getBins() as $service_id => $cache_backend) {
          $cache_backend->deleteAll();
        }
  5. So I think we can be a bit more definitive in our wording :)

  6. +++ b/core/modules/tour/src/Plugin/HelpSection/TourHelpSection.php
    @@ -0,0 +1,134 @@
    +      $container->get('entity_type.manager')
    ...
    +    $tags = $this->entityTypeManager->getDefinition('tour')->getListCacheTags();
    ...
    +    $tour_storage = $this->entityTypeManager->getStorage('tour');
    

    Random observation: there are some examples in core where we are more explicit about the dependencies. I.e. instead of injecting the whole entity-type manager, we only inject the elements which the plugin truly depends on - which is the tour storage handler and the tour entity type. See \Drupal\comment\Plugin\Menu\LocalTask\UnapprovedComments for example. In those instances the constructor argument contains the explicit dependencies and the static factory takes care of fetching those directly from the entity-type manager, rather than injecting the whole shebang. This means you only need to mock the real dependencies instead of the behaviour of the entity-type manager if writing a unit test. Probably best to get a third opinion on that, I recall @timplunkett having an opinion on it in other issues, but can't remember which way he preferred.

  7. +++ b/core/modules/tour/src/Plugin/HelpSection/TourHelpSection.php
    @@ -0,0 +1,134 @@
    +          $url = Url::fromRoute($route['route_name'], $params);
    ...
    +          $topics[$id] = Link::fromTextAndUrl($title, $url)->toString();
    

    random observation, if we injected the link and url generators instead of using the static factories, we could unit-test this method, instead of a web test - take this observation with a grain of salt, won't hold issue up on it

  8. +++ b/core/modules/tour/src/Tests/TourHelpPageTest.php
    @@ -0,0 +1,146 @@
    +    return [ ['Adding languages', 'Language'], ['Editing languages', 'Translation']];
    

    is the additional space normal here? [ [ ?

jhodgdon’s picture

Issue summary: View changes
StatusFileSize
new32.48 KB
new4.36 KB

Thanks for the review in #14! Your points:

1. I wouldn't want the default to be 'access administration pages'. The default would want to be '', because the admin/help page already only is visible for 'access administration pages', so we don't need that permission. Anyway, I made that default and updated the docs.

2. We do not actually send the output of the page into an alter hook, beyond the generic theme/render hooks that all render arrays get passed to. hook_help_section_info_alter() is for the plugin definition arrays (which basically means it gets the information in the @HelpSection annotations).

3. Updated docs. This reminded me that the tours section should probably be given the user context as it is checking permissions, so added that too.

4. Hah. Updated docs. There's an edge case possible where a module adds hook_help() when it didn't have it before, but developers doing that would know they need to clear cache if so, and if it's an update they downloaded they should run update.php which also clears the cache. So. Took out 'hopefully'. :)

5. Hm. Deferring that (if necessary) for now. I see what you're saying, don't see a compelling reason to do it for the moment. I would not expect this plugin class to be instantiated except when building the admin/help page, and I don't see a compelling reason to do it one way vs. the other, since both pieces (cache and topics) will be called for, one after the other, in any case.

6. I didn't make this change. The reasoning I used:
- The static Link and Url calls return you objects that are lazy-evaluated at render time, unlike the URL and Link generator classes. In the HookHelpSection class, that is desirable, I think, to leave the links as late as possible to render.
- In the Tour class, the link is rendered immediately to verify there is not an exception, so we could use the link generator service. However, the URL object is also used -- we call the ->access() method on it. It would mean injecting at least one and maybe several other services to replace everything the URL object is doing, and I think the code would be much less clear, because the object returned by the URL service is not something you can pass into the Link generator (which is expecting a regular Url object, not what the URL service returns)... so I'm inclined to leave it as it is.
So the bottom line is that the code in these plugins is quite a bit clearer and cleaner using Url and Link, and I'm inclined to leave it as it is. Thoughts?

7. Typo, fixed.

Anyway, here's a new patch and interdiff. Also updated issue summary to reflect plugin vs. hook.

vijaycs85’s picture

Issue summary: View changes
StatusFileSize
new285.47 KB

Looks great.

Few comments/ideas:

  1. diff --git a/core/modules/help/help.api.php b/core/modules/help/help.api.php
    

    Is this time to introduce help_ui module so that we can load all UI stuff there and leave help module with basic UI and option to provide help?

    This(help_ui) module can provide a help page with search field to look for help topics and help sections (document, tour, etc). more like this:

  2. +++ b/core/modules/help/help.api.php
    @@ -56,5 +56,24 @@ function hook_help($route_name, \Drupal\Core\Routing\RouteMatchInterface $route_
    + * Sections for the admin/help page are provided by plugins. This hook allows
    

    minor: when I read admin/help, I read it like "admin or help" page. Can we say "help index" page or something like that?

  3. +++ b/core/modules/help/src/Plugin/HelpSection/HookHelpSection.php
    @@ -0,0 +1,95 @@
    + *   id = "hook_help",
    

    this can be just "help"

    +++ b/core/modules/help/help.api.php
    @@ -56,5 +56,24 @@ function hook_help($route_name, \Drupal\Core\Routing\RouteMatchInterface $route_
    +  $info['hook_help']['header'] = t('Overviews of modules');
    

    'hook_help' need to updated as well.

  4. +++ b/core/modules/tour/src/Plugin/HelpSection/TourHelpSection.php
    @@ -0,0 +1,136 @@
    + *   permission = "access tour"
    

    I think this is the first time we have permission in plugin. is this restricting to use user_permission access?

  5. +++ b/core/modules/tour/src/Plugin/HelpSection/TourHelpSection.php
    @@ -0,0 +1,136 @@
    +        catch (\Exception $e) {
    

    Are we going to handle the route with param in future? or this is how it will be? can we add some more documentation why it doesn't work for our case?

  6. Don't have any tour, but just text "View edit page" (ref: 10824282-screenshot-16.png). Is it just me?
vijaycs85’s picture

StatusFileSize
new59.53 KB
jhodgdon’s picture

StatusFileSize
new32.95 KB
new1.95 KB

Thanks for the review in #16! Regarding your points...

1. The modifications here are fairly straightforward (making the page flexible and listing tours if available); if we get crazy with graphics and stuff (on another issue presumably) then we can think about splitting it up, but I don't see a point to having a Help module with a separate UI module at this point.

2. Updated to make it clearer this is a URL but I think saying "help index page" is confusing, as it is not called that anywhere else. The wording in this section is now parallel to the existing docs in hook_help() above in that same file.

3. I specifically used "hook_help" for this plugin because it is listing the pages provided by hook_help(). I'd prefer to leave it with this ID.

4. The permission annotation is documented on the Annotation class included in the patch.

5. There is no way to handle routes with parameters. I've made another try at updating the docs to make this clearer.

6. There are not many tours that exist currently in core. If you turn on the language/locale modules, you will have a few more showing. See #1921152: META: Start providing tour tips for other core modules., which hasn't been updated in a while.

Anyway, here's a patch with some updated docs...

wim leers’s picture

Status: Needs review » Needs work

WTF, I posted a comprehensive review that I spent 30 minutes on, but it's not here! :( :( :(

Here's a short version with the most crucial points.

  1. +++ b/core/modules/help/src/Controller/HelpController.php
    @@ -40,62 +51,91 @@ public function __construct(RouteMatchInterface $route_match) {
    +          // Users will have different permissions for various sections of this
    +          // page, so we must cache the page per user.
    +          'user',
    ...
    +      if (!empty($plugin_definition['permission']) && !$this->currentuser()->hasPermission($plugin_definition['permission'])) {
    

    user.permissions, not user, because varying by permissions, not something user-specific.

  2. +++ b/core/modules/help/src/Controller/HelpController.php
    @@ -40,62 +51,91 @@ public function __construct(RouteMatchInterface $route_match) {
    +  protected function fourColumnList($items) {
    

    This feels very, very wrong. Hardcoding a layout in a method name is bizarre.

    It feels this belongs in a template instead.

  3. +++ b/core/modules/help/src/HelpSectionPluginInterface.php
    @@ -0,0 +1,43 @@
    +  public function getCacheInformation();
    

    We have a standardized solution for this: \Drupal\Core\Cache\CacheableDependencyInterface.

  4. +++ b/core/modules/tour/src/Plugin/HelpSection/TourHelpSection.php
    @@ -0,0 +1,143 @@
    +    // The links are also checked for user access, so we need the current
    +    // user context as well.
    +    return ['tags' => $tags, 'contexts' => ['user']];
    

    Again user.permissions.

jhodgdon’s picture

Status: Needs work » Needs review
StatusFileSize
new32.24 KB
new32.24 KB

Thanks for the review in #20, and what a pain to lose your comment! Ugh. Anyway, I hope the important points are all there.

Regarding your point #2, the fact that it is a 4-column list is hard-coded in the way the method works (it divides up the list into 4 chunks) and in the class added to the render array (layout-column--quarter); that said, it does degrade nicely to a 1 or 2 column list on a phone or other smaller device. This is NOT a change from the existing help page; I only separated out the code that made the 4 columns into a separate method (I needed to call it on each section of the page rather than just once). And I changed the code slightly -- used array_slice() instead of the slightly more convoluted process used in the previous code for dividing the array into 4 parts.

Anyway... since this is how the existing page works, I think fixing that would be a separate issue. There were also two previous issues dealing with these points, which is how it got from the even worse code it used to be to where it is now:
#2408481: Rewrite help.module component's inline with our CSS standards
#2337317: Replace help page layout CSS with reuseable layout classes

By the way, regarding the search feature for help mentioned in the review in #16, there is already an issue related to this at #102254: Add an administrative search feature.

The attached patch/interdiff I think fixes the other points in #20. Much better to use that interface, excellent idea! I decided that it merited making a base class now (as was suggested a few reviews ago by larowlan), since I think most implementers will have now 2-3 methods they will want to leave empty.

jhodgdon’s picture

StatusFileSize
new8.78 KB

whoops, uploaded the patch twice instead of the interdiff. :(

jhodgdon’s picture

StatusFileSize
new33.58 KB

Dang, can't do anything right today. I also forgot to add the new plugin base class into that patch. It's also not in the interdiff file.

The last submitted patch, 20: 2661200-20.patch, failed testing.

The last submitted patch, 20: 2661200-20.patch, failed testing.

jhodgdon’s picture

StatusFileSize
new33.39 KB
new1.33 KB

One more patch - cleaning up some extraneous use statements, at least I hope they are extraneous. The tests are passing, and I think/hope I've addressed most/all of the review comments.

So... I really appreciate all of the reviews so far... Anyone have more suggestions for things that should be improved in this patch? I would like to get this patch finalized soon if possible so that I can work on #2351991: Add a config entity for a configurable, topic-based help system and hopefully make the 8.1 deadline at the end of the month (that issue is postponed on this one).

andypost’s picture

Issue tags: +Needs change record

Looks great!
The main question about performance cos new plugins and hook.

  1. +++ b/core/modules/help/help.api.php
    @@ -56,5 +56,24 @@ function hook_help($route_name, \Drupal\Core\Routing\RouteMatchInterface $route_
    +function hook_help_section_info_alter(&$info) {
    

    one more alter for each help block?

  2. +++ b/core/modules/help/src/Controller/HelpController.php
    @@ -40,62 +51,88 @@ public function __construct(RouteMatchInterface $route_match) {
    +    foreach ($plugins as $plugin_id => $plugin_definition) {
    +      // Check the provided permission.
    +      if (!empty($plugin_definition['permission']) && !$this->currentuser()->hasPermission($plugin_definition['permission'])) {
    +        continue;
    ...
    +      $this_output = [
    +        '#theme' => 'help_section',
    +        '#title' => $plugin_definition['header'],
    

    maybe better use #access for render then skip content

  3. +++ b/core/modules/help/src/HelpSectionPluginInterface.php
    @@ -0,0 +1,33 @@
    +  public function listTopics();
    +}
    

    nit, extra line

  4. +++ b/core/modules/help/src/Plugin/HelpSection/HelpSectionBase.php
    @@ -0,0 +1,44 @@
    +abstract class HelpSectionBase extends PluginBase implements HelpSectionPluginInterface, ContainerFactoryPluginInterface {
    

    HelpSectionPluginBase looks better name

  5. +++ b/core/modules/help/src/Plugin/HelpSection/HookHelpSection.php
    @@ -0,0 +1,81 @@
    +    $this->stringTranslation = $translation;
    ...
    +      $container->get('string_translation'),
    

    is not used?!

jhodgdon’s picture

StatusFileSize
new4.03 KB
new33.46 KB

Thanks for the review in #26 @andypost!

1. All plugin managers have an "alter" hook to let modules alter the list of plugins, so I put one in here. It is possible that not all of the alter hooks we have are documented, but all/most plugin managers have that alter hook invoke in them... This is pretty much a standard plugin manager construct method (in HelpSectionManager class):

+  public function __construct(\Traversable $namespaces, CacheBackendInterface $cache_backend, ModuleHandlerInterface $module_handler) {
+    parent::__construct('Plugin/HelpSection', $namespaces, $module_handler, 'Drupal\help\HelpSectionPluginInterface', 'Drupal\help\Annotation\HelpSection');
+
+    $this->alterInfo('help_section_info');
+    $this->setCacheBackend($cache_backend, 'help_section_plugins');
+  }

So this alter is being invoked once when you go to the admin/help page, when someone asks for the HelpSectionManager to list all the help page section plugins. It will only actually get invoked if the help section plugins list cache data has been invalidated or is missing. It is not affecting the help block, so it is not happening on every page, just when you go to admin/help, if someone has installed/uninstalled a module or cleared the cache or something.

2. The page is being cached based on user permissions already. So it seems to be a waste of time to calculate something that will never be displayed? I think it's better just to skip it if the user cannot see it anyway. (You brought up several performance issues -- why add to the problem?)

3. Fixed. Thanks!

4. Good idea. Fixed. Thanks! [Note that the interdiff doesn't show that the HelpSectionBase class file name changed, due to me not being great at creating interdiff files, but it is changed in the patch.]

5. Hm. That HookHelpSection plugin extends PluginBase, which uses StringTranslationTrait. So it seemed like a good idea to inject the string translation service, in case any methods on PluginBase used t()? Not sure. Is this unnecessary?

6. (implied) I'll get a change record drafted shortly and update the issue.

So... somewhat flawed interdiff file and new patch attached. I haven't tested the patch but it should be OK hopefully.

jhodgdon’s picture

Issue tags: -Needs change record
larowlan’s picture

Only one thing left here from my point of view

  1. +++ b/core/modules/help/src/Annotation/HelpSection.php
    @@ -0,0 +1,65 @@
    +  public $permission = '';
    

    I still think this should default to 'access administration pages' and not ''. Then implementations can omit that key rather than pass ''

  2. +++ b/core/modules/tour/src/Plugin/HelpSection/TourHelpSection.php
    @@ -0,0 +1,147 @@
    +  public function getCacheTags() {
    

    This is nice

jhodgdon’s picture

Regarding #29, they can already omit the permission key. In fact, if you look at HookHelpSection, it does that:

+ * @HelpSection(
+ *   id = "hook_help",
+ *   header = @Translation("Module overviews"),
+ *   description = @Translation("Module overviews are provided by modules. Overviews available for your installed modules:"),
+ * )

which, according to the docs in the annotation, means:

+  /**
+   * The (optional) permission needed to view the help section.
+   *
+   * Only set if this section needs its own permission, beyond the generic
+   * 'access administration pages' permission needed to see the admin/help page
+   * itself.
+   *
+   * @var string
+   */
+  public $permission = '';

To clarify, the admin/help page is already checked for the 'access administration pages' permission due to the help.routing.yml entry:

help.main:
  path: '/admin/help'
  defaults:
    _controller: '\Drupal\help\Controller\HelpController::helpMain'
    _title: 'Help'
  requirements:
    _permission: 'access administration pages'

So if a HelpSection plugin has no permission beyond that, it just leaves 'permission' out of the annotation, and no further permission check is performed. There is no reason to default the permission in the annotation to 'access administration pages', and check that same permission again in the Controller when it decides whether to build each section. Right?

larowlan’s picture

Issue summary: View changes
Status: Needs review » Needs work
StatusFileSize
new35.32 KB

Thanks @jhodgdon - works for me

I manually tested this and encountered #16.6 - I think we should skip that section if there are no tour links.

The reason we get 'view edit page' with nothing else (no link) is because that tour is for editing a view - and we obviously don't have the context of a view (hence your try/catch block). In those instances I think we should just skip it, not output plain text - thoughts?

Sample with extra modules enabled - looks great

But, again here I think the 'editing languages' one should go.

jhodgdon’s picture

Status: Needs work » Needs review

Actually, I don't agree about the tours and plain text vs. links. I put that in because I really do think it's useful to know that a tour exists for the "editing languages" or "editing a view" pages, even though you cannot go there directly with a link. The description text for the section explains this with:

The tours with links in this list are on user interface landing pages; the tours without links will show on individual pages (such as when editing a View using the Views UI module).

Eventually it would be nice if #1921152: META: Start providing tour tips for other core modules. got done so we had a sensible list of tours on the page in the first place, so that the section wouldn't be so empty...

And as a note, the Help controller does have some code that outputs some text if the list of topics is empty, but again I think having the section there is useful.

Thoughts?

jhodgdon’s picture

RE #31/#32, we should let the Tour module maintainer decide this.

jhodgdon’s picture

Issue tags: +Needs usability review

and/or Usability team.

andypost’s picture

Permission to display section or content within?
If we gonna reuse help for customer's website page then we need control how to hide some sections for roles at lest

jhodgdon’s picture

The permission in the plugin annotation is the permission to display the entire section. Any module that provides a section plugin can define a permission like "View xyz type of help" and then that can be given to a role. A user without that permission won't see the section at all. For instance, if you don't have the tour viewing permission, you won't see the Tours section, because the Tour plugin has that permission in its annotation.

Then each individual plugin may also check permission or do whatever it wants when making the list of topic links (so it could hide certain links for certain permissions etc.). It can also put whatever it wants into the cache metadata for the section, using the methods on the plugin. For instance, the Tour module adds the entity list cache tag for Tour config entities for its section. The controller adds the user.permission cache tag to the page as a whole, so that if a user with different permissions views the page, it will be rebuilt for them.

And then presumably the standard Drupal route permission system will be used when someone clicks on a link to view a topic or see a tour or whatever. So for instance if the Tours section lists a tour for adding a language, and someone who doesn't have administer languages permission clicks on the link, they get a 403.

On the other hand, the core hook_help() pages don't do much about permissions -- this system was in place prior to this patch. You just need the standard "access administration pages" permission to view admin/help or any of the hook_help() module pages like admin/help/node. This is true whether or not you have any admin permissions for that particular module; for instance, you can read about how to add content on the admin/help/node page, even if you have no add content permissions. But that has been true forever in Core. This patch doesn't change it in any way.

So... I think the system is sufficiently flexible for whatever use case, and the tests in this patch do test the permissions stuff...

wim leers’s picture

Issue tags: +Needs reroll

#32: I'd love to do it for the Text Editor and Quick Edit modules. But I've been told I should postpone working on tours because a set of rules/style guide/something like that was being created for tours first?


#2665672: hook_help() should allow render arrays for return values landed, patch in #27 doesn't apply anymore.


  1. +++ b/core/modules/help/help.api.php
    @@ -56,5 +56,24 @@ function hook_help($route_name, \Drupal\Core\Routing\RouteMatchInterface $route_
    + * Sections for the page at admin/help are provided by plugins. This hook allows
    
    +++ b/core/modules/help/src/Annotation/HelpSection.php
    @@ -0,0 +1,65 @@
    +   * 'access administration pages' permission needed to see the admin/help page
    

    admin/help -> /admin/help

  2. +++ b/core/modules/help/src/Controller/HelpController.php
    @@ -40,62 +51,88 @@ public function __construct(RouteMatchInterface $route_match) {
    +        $this_output['#links'] = $this->fourColumnList($links);
    ...
    +   * Makes a four-column list of items.
    ...
    +    for ($i = 0; $i < 4; $i++) {
    +      $slice = array_slice($items, $break * $i, $break);
    +      $output[$i] =  [
    +        '#type' => 'container',
    +        '#attributes' => ['class' => ['layout-column', 'layout-column--quarter']],
    +        'links' => [
    +          '#theme' => 'item_list',
    +          '#items' => $slice,
    +        ],
    +      ];
         }
     
    
    +++ b/core/modules/help/templates/help-section.html.twig
    @@ -0,0 +1,17 @@
    +<h2>{{ title }}</h2>
    +<p>{{ description }}</p>
    +{{ links }}
    

    I stand by #19.2.

    I think this should all be taken care of in help-section.html.twig.

    I think this needs to get sign-off from Cottser for that.

  3. +++ b/core/modules/help/src/Plugin/HelpSection/HelpSectionPluginBase.php
    @@ -0,0 +1,44 @@
    +  /**
    +   * {@inheritdoc}
    +   */
    +  public function getCacheContexts() {
    +    return [];
    +  }
    +
    +  /**
    +   * {@inheritdoc}
    +   */
    +  public function getCacheTags() {
    +    return [];
    +  }
    +
    +  /**
    +   * {@inheritdoc}
    +   */
    +  public function getCacheMaxAge() {
    +    return [];
    +  }
    

    The getCacheMaxAge() implementation is wrong. Once it's fixed, it'll be identical to use \Drupal\Core\Cache\UnchangingCacheableDependencyTrait. So I suggest doing that instead.

  4. +++ b/core/modules/help/src/Tests/HelpTest.php
    @@ -81,10 +82,25 @@ public function testHelp() {
    +    // Verify that the order of topics is alphabetical by displayed module
    +    // name, by checking the order of some modules, including some that would
    

    Nit: 80 cols.

  5. +++ b/core/modules/tour/src/Plugin/HelpSection/TourHelpSection.php
    @@ -0,0 +1,147 @@
    +  public function getCacheTags() {
    +    // The list of topics depends on the list cache tag for the tour entity.
    +    return $this->entityTypeManager->getDefinition('tour')->getListCacheTags();
    +  }
    

    Yay :)

    Just one detail: s/tour entity/tour entity type/.

  6. +++ b/core/modules/tour/src/Plugin/HelpSection/TourHelpSection.php
    @@ -0,0 +1,147 @@
    +    /** @var \Drupal\Core\Entity\EntityStorageInterface $tour_storage */
    

    This one is unnecessary.

  7. +++ b/core/modules/tour/src/Plugin/HelpSection/TourHelpSection.php
    @@ -0,0 +1,147 @@
    +    // Sort in the manner defined by Tour (by tour title).
    

    Why not omit the part between parentheses in case it changes some day?

  8. +++ b/core/modules/tour/src/Plugin/HelpSection/TourHelpSection.php
    @@ -0,0 +1,147 @@
    +    foreach ($entities as $entity) {
    

    Would $tours instead of $entities be better?

  9. +++ b/core/modules/tour/src/Plugin/HelpSection/TourHelpSection.php
    @@ -0,0 +1,147 @@
    +          // Skip this route if the current user cannot access it.
    +          if (!$url->access()) {
    +            continue;
    +          }
    

    This is a big cacheability bug: the cacheability metadata for being able to access a URL is simply thrown away. It means we may divulge links to routes the current user cannot access.

    listTopics() is allowed to return a list of links or a list of render arrays. But that's not good enough; we need to be able to specify the cacheability of the list of topics itself, which then can describe why some links are absent/present

jhodgdon’s picture

Status: Needs review » Needs work
StatusFileSize
new33.47 KB

Thanks Wim! First, here's a reroll to merge with the changes from the other issue. And regarding creating tours, see that meta issue linked in #32.

I'll address/fix the rest in a separate patch because I have enough problems with interdiffs as it is without combining them with rerolls. ;)

wim leers’s picture

I'll address/fix the rest in a separate patch because I have enough problems with interdiffs as it is without combining them with rerolls. ;)

It's simpler to review too then. Simplicity++ :)

jhodgdon’s picture

StatusFileSize
new34.23 KB
new9.39 KB

So, regarding the review in #37 -

1. Fixed in two places in this file, plus a few other files. Some rewrapping required.

2. I do agree with you on this... I talked to joelpittet about this, and got it working. There's a new template for help sections for Classy (to override the default one in modules/help and Stable theme), and now HelpController does not output markup itself. Woot! Definitely an improvement.

3. Excellent, thanks for pointing that out!

4. I don't see any comments going over 80-character lines in this patch... ???

5. Fixed.

6. Got rid of the local variable entirely -- combined it with the next line of code.

7. Good idea, fixed.

8. Sure, also changed $entity to $tour.

9. Hm. So. In TourHelpSection, getCacheContexts() does this:

  public function getCacheContexts() {
    // The links are checked for user access, so we need the user
    // permissions context.
    return ['user.permissions'];
  }

I thought that this would take care of making sure that the page cache is not shared for people with different permissions to access routes?

If not, do you have a suggestion for what I need to do instead? Thanks!

Anyway. Here's a new patch and an interdiff. Tested manually and works fine; let's see if the bot agrees.

Note: The interdiff does not include the new core/themes/classy/templates/misc/help-section.html.twig template file, because I am lousy at creating interdiffs that contain both new files and changes to existing files. Sorry about that!

dimaro’s picture

Status: Needs work » Needs review
Issue tags: -Needs reroll

Update to run the tests.

jhodgdon’s picture

Whoops. Forgot that. Thanks!

jhodgdon’s picture

StatusFileSize
new1010 bytes
new34.37 KB

Doh. Realized that in Stable/core template, since we are not outputting an item-list of links any more, I needed to add something to the template. Verified manually that the page works in Stark theme as well. It just makes a simple UL list of the topics.

wim leers’s picture

Assigned: jhodgdon » catch
  1. SO MUCH BETTER! <3
  1. Not going over, but under. There's a word on the second line that I quoted that fits on the previous line.
  1. I thought that this would take care of making sure that the page cache is not shared for people with different permissions to access routes

    Yes, that takes care of varying by permissions.

    But this is doing something else entirely: this is showing URLs that the user may not be able to access. Being forbidden access to a certain URL can depend on anything, it could be per user, it could be time-based, it could depend on some configuration, etc. It is that cacheability metadata that is missing. Which means that if /admin/help eventually is cached (which is unlikely because not very useful), or if something else starts calling TourHelpSection (which is more likely), then the cacheability metadata will be incomplete.

    We've solved such problems in the past by introducing a value object that contains the list of things and then also contains cacheability metadata for the overall list. An example is \Drupal\Core\Breadcrumb\Breadcrumb, which as it happens also contains a list of Links.

    But perhaps it's okay to ignore this because this is something that is not really ever worth caching. However, if we conclude that, then we'd need to specify max-age=0.

    (I was first tempted to say "well there's no harm in disclosing a certain URL to people who clearly already are site builders, the worst thing that can happen is a 403". But if this were to be cached, then it'd be vary by user.permissions, which means that if user A cannot see link 3, but user B can, and users A and B have the same roles/permissions, then the output for both will depend on which user first accesses /admin/help.)

    Assigning to catch for feedback for this aspect.

Other than that pesky detail, this patch now looks splendid I think :)

wim leers’s picture

Issue tags: +D8 cacheability

Tagging for #37.9 + #40.9 + #44.9.

jhodgdon’s picture

StatusFileSize
new34.41 KB
new1.3 KB

Hm. I see what you are saying about the cacheability -- thanks for taking the time to explain it.

So, let's see. The TourHelpSection plugin is trying to make a link to any of the possibly several routes (usually admin pages) that the tour could be displayed on (for instance, a tour could apply to both the Add and Edit page for some config entity, which means it has 2 routes it applies to).

So TourHelpSection does a loop over the routes, where it checks if the route:

a) Can be created (without any special route parameters).
b) Can be accessed by the current user.

If the first route works, it makes that link. If it doesn't, it keeps trying the rest of the routes where that tour can be displayed, until it finds one and makes that link. If none of them works, it just displays the tour title as not a link. So this isn't quite like breadcrumbs, because they have definite URLs at least.

Yeah, I'm inclined to say that the TourHelpSection's list of links should not be cached.

Here's one more patch... I was going to fix the 80-char thing in HelpTest but with the comma, it hits 80 in my editor so I didn't.

tim.plunkett’s picture

  1. +++ b/core/modules/help/src/Plugin/HelpSection/HelpSectionPluginBase.php
    @@ -0,0 +1,25 @@
    +abstract class HelpSectionPluginBase extends PluginBase implements HelpSectionPluginInterface, ContainerFactoryPluginInterface {
    

    I don't think this should implement ContainerFactoryPluginInterface. That should be up to each implementation.

  2. +++ b/core/modules/help/src/Plugin/HelpSection/HookHelpSection.php
    @@ -0,0 +1,81 @@
    +class HookHelpSection extends HelpSectionPluginBase {
    +
    +  /**
    ...
    +    $this->stringTranslation = $translation;
    ...
    +      $container->get('string_translation'),
    

    This is no longer using StringTranslationTrait, so it doesn't need the service injected.

  3. +++ b/core/modules/tour/src/Plugin/HelpSection/TourHelpSection.php
    @@ -0,0 +1,141 @@
    +class TourHelpSection extends HelpSectionPluginBase {
    ...
    +    $this->stringTranslation = $translation;
    ...
    +      $container->get('string_translation'),
    

    Same here, AFAICS

jhodgdon’s picture

StatusFileSize
new5.06 KB
new33.93 KB

Thanks Tim for the review in #47 -- all good points.

Someone brought up the Translation thing before... I wasn't sure because PluginBase itself uses StringTranslationTrait, which kind of implies it expects the translation service to be included. But if you say it's OK not to do that, ... yeah I would not see anything PluignBase would want to translate. Good! Took that out.

And you are right, we should leave it up to the plugin whether they need to be container-aware or not. I think most would, but you never know, maybe Advanced Help will use this and do some kind of file system listing. :)

So here's yet one more patch... tested manually, still works... Hopefully this is converging to something Good For Drupal 8 (TM).

I just want to say again, I REALLY appreciate all the excellent reviews from everyone! Definitely improving the patch, little by little. ;)

jhodgdon’s picture

I just made a minor update to the change record for this issue. Also updating the issue summary to add a few points.

jhodgdon’s picture

wim leers’s picture

StatusFileSize
new35.02 KB
new2.08 KB

#46: there is one alternative approach I thought of after having written #44.9 (actually, while trying to sleep :P). That approach is to not let HelpSectionPluginInterface::listTopics() return a list, but let it return a render array. But that also doesn't really make sense. It also limits the ability to reuse the information.

So I think max-age=0 solution you went with in #46 is fine. But the comment that you added is not really accurate: we can actually get that information. You're just choosing not to deal with it, by setting max-age=0.

BUT! Having written this made me think of one more approach that you may find more acceptable: pass a CacheableMetadata object into listTopics(), and let it be altered. This object then contains the cacheability metadata of the list. \Drupal\Core\Menu\MenuLinkTree::buildItems() and \Drupal\Core\Menu\MenuParentFormSelectorInterface::getParentSelectOptions() also use this for example.


I was going to RTBC this and fix my own nits. But now I'm going to reroll it twice: first to fix my nits below (points 1–3). And then I'm going to implement the suggested approach I gave in the "BUT!"-paragraph (which also fixes point 4 below). Then you can see what that would look like. That then only leaves point 5 below to be answered by you.

  1. +++ b/core/modules/help/src/Controller/HelpController.php
    @@ -40,62 +51,57 @@ public function __construct(RouteMatchInterface $route_match) {
    +    // We are checking permissions, so add the user.permission cache context.
    

    s/user.permission/user.permissions/

  2. +++ b/core/modules/help/src/Controller/HelpController.php
    @@ -40,62 +51,57 @@ public function __construct(RouteMatchInterface $route_match) {
    +    $cache_metadata->addCacheableDependency($this->helpManager);
    

    99% of the time we call this $cacheability. Let's do that here too.

  3. +++ b/core/modules/help/src/Plugin/HelpSection/HelpSectionPluginBase.php
    @@ -0,0 +1,24 @@
    +  use UnchangingCacheableDependencyTrait;
    +}
    

    Needs newline in between.

  4. +++ b/core/modules/tour/src/Plugin/HelpSection/TourHelpSection.php
    @@ -0,0 +1,137 @@
    +    // permissions and other factors. Unfortunately, we don't have a way to
    +    // get cachability metadata for each of these factors, so the only way
    +    // to ensure that inapplicable information is not used is to forbid
    +    // caching completely.
    

    This information is wrong.

  5. +++ b/core/themes/classy/templates/misc/help-section.html.twig
    @@ -0,0 +1,50 @@
    + * Classy theme implementation for a section of the help page.
    

    Are you sure you want this to live in Classy? Shouldn't this be the default template, i.e. the one in core/modules/help/templates? If it were, then it would work for all themes, not just Seven.

wim leers’s picture

StatusFileSize
new41.84 KB
new4.35 KB
new6.36 KB
new10.62 KB

To do what I said in #51, I also had to change Url::access() and Url::renderAccess(). Both of those still relied on booleans, they did not support AccessResultInterface objects yet. Both of those methods were introduced in #2302563: Access check Url objects. We forgot to update them in #2287071: Add cacheability metadata to access checks, probably because that last issue landed so shortly after the first issue, it was simply overlooked.

So:

  1. interdiff-relevant-changes.txt contains the actual changes relevant to this patch. Basically, what I proposed.
  2. interdiff-url-changes.txt contains the changes to \Drupal\Core\Url and its test coverage, basically to fix an oversight. This doesn't break BC.
  3. interdiff.txt contains all changes.
wim leers’s picture

I'd like @catch's feedback on #52.

dawehner’s picture

  1. +++ b/core/lib/Drupal/Core/Url.php
    @@ -818,11 +825,11 @@ public function access(AccountInterface $account = NULL) {
        * @param array $element
        *   A render element as returned from \Drupal\Core\Url::toRenderArray().
        *
    -   * @return bool
    -   *   Returns TRUE if the current user has access to the url, otherwise FALSE.
    +   * @return \Drupal\Core\Access\AccessResultInterface
    +   *   The access result.
        */
       public static function renderAccess(array $element) {
    -    return $element['#url']->access();
    +    return $element['#url']->access(NULL, TRUE);
    

    How is that not a API break?

  2. +++ b/core/modules/help/src/Annotation/HelpSection.php
    @@ -0,0 +1,65 @@
    +   * The header of the help page section.
    +   *
    +   * @var \Drupal\Core\Annotation\Translation
    +   *
    +   * @ingroup plugin_translatable
    +   */
    +  public $header;
    

    Header is a bit confusing, because you would think, you could put some HTML there, but its just a title, isn't it?

  3. +++ b/core/modules/help/src/Controller/HelpController.php
    @@ -40,62 +51,57 @@ public function __construct(RouteMatchInterface $route_match) {
    +        '#title' => $plugin_definition['header'],
    +        '#description' => $plugin_definition['description'],
    

    IMHI we should just use the plugin instance here as well. No reason to deal with the array structure, unless needed.

  4. +++ b/core/modules/help/src/HelpSectionPluginInterface.php
    @@ -0,0 +1,40 @@
    +/**
    + * Provides an interface for a plugin for a section of the /admin/help page.
    + *
    + * Plugins of this type need to be annotated with
    + * \Drupal\help\Annotation\HelpSection annotation, and placed in the
    + * Plugin\HelpSection namespace directory. They are managed by the
    + * \Drupal\help\HelpSectionManager plugin manager class. There is a base
    + * class that may be helpful:
    + * \Drupal\help\Plugin\HelpSection\HelpSectionPluginBase.
    + */
    +interface HelpSectionPluginInterface extends PluginInspectionInterface, CacheableDependencyInterface {
    

    IMHO we should also add the description/header on here?

wim leers’s picture

  1. Because it's a valid return value for #access_callback: such callbacks return either booleans or AccessResultInterface objects. It now becomes the latter.

    However, if you want to be 100% safe, we could add a new public static function renderAccessWithCacheability() method that does this, and make toRenderArray() use that instead. Then any code that is currently calling renderAccess() directly would get the exact same result.

jhodgdon’s picture

Status: Needs review » Needs work

I like the approach in the latest patches.

So... Regarding #52 - item 5 -- there is a simple template that is in the Stable theme (as well as a core template), which has no classes. Templates with classes belong in Classy, so that is where the 4-column theme template goes. I think Cottser/joelpittet would agree on this being the right thing to do, or at least that is what I thought Joel told me to do in IRC yesterday. Anyway, the help page *does* work in Stark, in the sense that the topics are listed on the page with no stying decisions (such as dividing the page up into exactly 4 columns, which is Classy's choice and should not be part of Stark).

Anyway, a few nitpicks on the latest patch, and 1 substantive comment:

  1. +++ b/core/lib/Drupal/Core/Url.php
    @@ -801,15 +802,21 @@ public function getInternalPath() {
    +   *   The access result. Returns a boolean if $return_as_object is FALSE (this
    +   *   is the default) and otherwise an AccessResultInterface object.
    +   *   When a boolean is returned, the result of AccessInterface::isAllowed() is
    +   *   returned, i.e. TRUE means access is explicitly allowed, FALSE means
    +   *   access is either explicitly forbidden or "no opinion".
    

    In text, "boolean" should be "Boolean". It is a proper noun.

    Also, as you pointed out on one of my earlier patches, in the 3rd line the word "When" can be moved up to the previous line. ;) Funny how we can see this in someone else's patch...

    Also, in the 4th line, please do not use "i.e.". It is not punctuated properly and most people (as demonstrated by many many many examples of bad docs that we have laboriously fixed in Core) do not understand what it means. Really, we had some docs cleanup issues that got rid of all of the i.e. and e.g. in Core docs... it was painful to see how bad they were. So, please use "that is". And the punctuation should be:

    ... is returned; that is, TRUE means...

  2. +++ b/core/lib/Drupal/Core/Url.php
    @@ -818,11 +825,11 @@ public function access(AccountInterface $account = NULL) {
    -   * @return bool
    -   *   Returns TRUE if the current user has access to the url, otherwise FALSE.
    +   * @return \Drupal\Core\Access\AccessResultInterface
    +   *   The access result.
        */
       public static function renderAccess(array $element) {
    

    I agree with dawehner here -- this definitely smells like a BC break.

    This is a public static function. Changing its return value cannot be OK. You cannot guarantee me that no one can call this outside the context where you say it doesn't break anything. If we need a function that returns an object then we should define a new function, not break BC on the old one, right?

  3. +++ b/core/modules/help/src/Controller/HelpController.php
    @@ -40,62 +51,57 @@ public function __construct(RouteMatchInterface $route_match) {
    +      $links = $plugin->listTopics($cacheability);
    

    I do think this is a good addition to the API. Thanks Wim! Although it is a bit weird to have two separate places to get cacheability information, one for the plugin itself and one for the listTopics() method. Is it not possible we could combine them?

  4. +++ b/core/modules/help/src/HelpSectionPluginInterface.php
    @@ -0,0 +1,40 @@
    +   * @param \Drupal\Core\Cache\CacheableMetadata &$cacheability
    

    We do not normally put & in @param doc lines. Please take that out.

  5. +++ b/core/modules/help/src/HelpSectionPluginInterface.php
    @@ -0,0 +1,40 @@
    +   *   Cacheability metadata object, which will be populated based on the
    +   *   accessibility of the list of topics.
    

    I would not use "accessibility" here. Accessibility to me means "can people who are blind or have mobility limitations use the page", not "do I have access permission to see it". At least, it's definitely ambiguous.

    Also, you are assuming that the only reason listTopics() has cacheability metadata is due to access checking, but it's certainly possible for some implementation of this plugin to do something else that requires cacheability, right?

    So.... my suggested wording here would be:

    Cacheability metadata object; add cacheability information about the returned list of topics to this object.

    But agin... I'm not really sure why we need to do the cacheability stuff in two places in this plugin. There's really only 1 use for this plugin -- making a list of topics. Why should the plugin and listTopics() have separate cacheability stuff? Can't we just combine them?

  6. +++ b/core/modules/help/src/HelpSectionPluginInterface.php
    @@ -0,0 +1,40 @@
    +  public function listTopics(CacheableMetadata &$cacheability);
    

    Is the & really necessary here? I think objects are always passed by reference. See
    http://php.net/manual/en/language.oop5.references.php

    I'm pretty sure we do not need the & here.

  7. +++ b/core/modules/help/templates/help-section.html.twig
    @@ -0,0 +1,25 @@
    + * Default theme implementation for a section of the help page.
    

    Just pointing out that this is the default template implementation (which by necessity is also copied in Stable theme).

    It just outputs a UL list of the links, not divided into 4 columns.

  8. +++ b/core/modules/tour/src/Plugin/HelpSection/TourHelpSection.php
    @@ -0,0 +1,140 @@
    +  public function getCacheMaxAge() {
    +    // The calculation of which URL (if any) gets put on which tour depends
    +    // on a route access check. This can have a lot of inputs, including user
    +    // permissions and other factors. Unfortunately, we don't have a way to
    +    // get cachability metadata for each of these factors, so the only way
    +    // to ensure that inapplicable information is not used is to forbid
    +    // caching completely.
    +    return 0;
    +  }
    

    So due to your patch for listTopics() just below, shouldn't we be taking this out?

  9. +++ b/core/modules/tour/src/Plugin/HelpSection/TourHelpSection.php
    @@ -0,0 +1,140 @@
    +          $cacheability->addCacheableDependency($url_access);
    +          if (!$url_access->isAllowed()) {
    +            continue;
    +          }
    +
    +          // Generate the link HTML directly, using toString(), to catch
    +          // missing parameter exceptions now instead of at render time.
    +          $topics[$id] = Link::fromTextAndUrl($title, $url)->toString();
    +          // If the line above didn't generate an exception, we have a good
    +          // link that the user can access.
    +          $made_link = TRUE;
    

    Technically, we are adding more cacheable dependencies than we really need here, because some of the routes will need parameters so will be try/catched out below, but that is probably OK.

  10. +++ b/core/themes/classy/templates/misc/help-section.html.twig
    @@ -0,0 +1,50 @@
    + * Classy theme implementation for a section of the help page.
    

    And this one is the Classy theme implementation, which is more complex: divides up into 4 columns and adds the column--quarter classes.

  11. +++ b/core/themes/stable/templates/admin/help-section.html.twig
    @@ -0,0 +1,25 @@
    + * Default theme implementation for a section of the help page.
    

    And then here's the Stable copy of the Core theme.

jhodgdon’s picture

And... I am now officially handing this over to Wim because I am NOT making changes to the Url object.

I was quite happy with the "don't cache the list of tours" solution. If we need to make this many changes in order to get proper cacheability information for a page we probably aren't going to cache anyway... why are we doing this?

Maybe we should just go back to the patch in #51, address dawenher's feedback in #54, and actually get this finished by the Beta deadline. I would really like to do that... thoughts? I fear that the current approach is not going to make it in at all (BC break and I don't understand the implications of removing it). I would really really really really not be happy if we miss the deadline over something that is probably not necessary.

jhodgdon’s picture

Status: Needs work » Needs review
StatusFileSize
new34.62 KB
new5.53 KB

OK... In case the approach in #57 is acceptable, here is a patch that:
- Starts with #51 [Wim's fixup of a few "nits" from my last patch in #48].
- Fixes the comment in the Tour plugin for the getMaxAge() method mentioned in #51, which wasn't fixed by Wim because he took a different approach in #52.
- Addresses @dawehner's feedback in #54, by adding getHeader() and getDescription() methods to the plugin interface and base class. Also clarifies the annotation docs for 'header'.
- Well, then I decided #54 was right and the annotation/method should be called 'title' instead of 'header', so I went and changed that.

Note that the interdiff is to #51.

After clearing the cache, the Help page still looks the same... so I expect it will pass the tests.

Thoughts? Further suggestions?

jhodgdon’s picture

Any thoughts on the latest patch/approach? Anything else left to do?

larowlan’s picture

Status: Needs review » Reviewed & tested by the community

I agree, changing Url object is out of scope here.

yoroy’s picture

The added flexibility to also show contrib help is good and useful. Listing the tours is a nice addition as well.

catch’s picture

Assigned: catch » Unassigned

fwiw I think it's fine to go with max_age = 0 for now. It's good to know if we ever wanted to we could add caching later, but this is neither the most expensive nor the most frequently visited of admin pages. Unassigning me for that bit, haven't done a full review here yet so just leaving RTBC.

jhodgdon’s picture

Two notes:
- RE #61 - I already have the Configurable Help (sandbox currently) contrib module using this patch to add editable help topic pages to admin/help... I was going to try to get that into Core too but this issue was a prereq and there is not time for 8.1.x. Maybe 8.2.x, since webchick (Product Manager) thinks it is a good idea. We'll see.
- RE #62 - only the Available Tours segment of the page is uncacheable currently. So if you don't have the Tour module enabled, admin/help is cacheable. And we can probably add cacheability to the Tours section later if it becomes important.

wim leers’s picture

fwiw I think it's fine to go with max_age = 0 for now. It's good to know if we ever wanted to we could add caching later, but this is neither the most expensive nor the most frequently visited of admin pages.

+1. As interdiff-relevant-changes.txt shows, it could be implemented in a way that maintains BC.


This patch looks great now. I just wanted @catch's sign-off on this wrt cacheability.

I have zero remarks left for this patch. Sorry that it took so long, @jhodgdon.

RTBC++


For #52.2's Url::access() changes — which are indeed out of scope here but are necessary anyway, I opened #2677902: Url::access() doesn't allow cacheability metadata to be bubbled.

mile23’s picture

Status: Reviewed & tested by the community » Needs review
StatusFileSize
new34.62 KB

Reroll of #58.

xjm’s picture

@jhodgdon and I discussed whether this issue could still go into 8.1.x, and I think this is definitely worth considering as a beta phase change. I didn't review the patch in detail, but the overall changes seem a possible beta target for me. Let's discuss committing to 8.1.x as well when we review this for commit.

jhodgdon’s picture

Status: Needs review » Reviewed & tested by the community

#65 appears to be exactly the same file as #58. diff finds no differences. ???

mile23’s picture

Oooookay so now the patch in #58 applies where it didn't 5 minutes ago.

RTBC +1, given testbot green.

wim leers’s picture

Issue tags: +beta target triage

+1 for the analysis in #66. I consider this very low risk.

Tagging beta target triage, akin to rc target triage.

star-szr’s picture

Minor coding standards and Twig docs stuff, leaving at RTBC:

  1. +++ b/core/modules/help/help.module
    @@ -44,6 +44,22 @@ function help_help($route_name, RouteMatchInterface $route_match) {
    +function help_theme($existing, $type, $theme, $path) {
    + return [
    +    'help_section' => [
    +      'variables' => [
    +        'title' => NULL,
    +        'description' => NULL,
    +        'links' => NULL,
    +        'empty' => NULL,
    +      ],
    +    ],
    + ];
    +}
    

    Minor: the return and closing square bracket line are indented by one space, should be two.

  2. +++ b/core/themes/classy/templates/misc/help-section.html.twig
    @@ -0,0 +1,50 @@
    + * Classy theme implementation for a section of the help page.
    
    +++ b/core/themes/stable/templates/admin/help-section.html.twig
    @@ -0,0 +1,25 @@
    + * Default theme implementation for a section of the help page.
    

    Minor: These should just say "Theme override for…"

  3. +++ b/core/themes/classy/templates/misc/help-section.html.twig
    @@ -0,0 +1,50 @@
    + *
    + * @ingroup themeable
    
    +++ b/core/themes/stable/templates/admin/help-section.html.twig
    @@ -0,0 +1,25 @@
    + *
    + * @ingroup themeable
    

    Minor: Our current policy is that @ingroup themeable should only be on the core templates although a handful of templates in Bartik, Classy, and Seven violate this.

xjm’s picture

Issue tags: -beta target triage

(We're not going to do any beta target triage process, so removing the tag. Committers are evaluating issues in the RTBC queue as normal. Thanks!)

wim leers’s picture

Sorry!

xjm’s picture

The committers all agreed to commit this to 8.1.x as well as 8.2.x. If the bot comes along to move it to 8.2.x before we get the chance, go ahead and move it back. :) Thanks!

xjm’s picture

Issue summary: View changes

I added the screenshots to the summary (are they current)?

Version: 8.1.x-dev » 8.2.x-dev

Drupal 8.1.0-beta1 was released on March 2, 2016, which means new developments and disruptive changes should now be targeted against the 8.2.x-dev branch. For more information see the Drupal 8 minor version schedule and the Allowed changes during the Drupal 8 release cycle.

xjm’s picture

Version: 8.2.x-dev » 8.1.x-dev

As per #74.

star-szr’s picture

StatusFileSize
new34.54 KB
new1.99 KB

Here is #70.

jhodgdon’s picture

Thanks @cottser for #77. Thanks @xjm for #76. And yes, the screen shots are current.

alexpott’s picture

Status: Reviewed & tested by the community » Needs work

This looks incredibly useful. It would be great to encourage more tours in core.

+++ b/core/modules/help/src/Controller/HelpController.php
@@ -40,62 +51,57 @@ public function __construct(RouteMatchInterface $route_match) {
+        '#empty' => $this->t('There is currently nothing in this section'),

There is no test coverage of this. We should create a test help plugin that has nothing to display.

jhodgdon’s picture

Assigned: Unassigned » jhodgdon

RE tours in Core, see this meta/strategy issue, which is stalled and needs to be restarted:
#1921152: META: Start providing tour tips for other core modules.

RE testing "nothing in section", good idea. I'll take care of it shortly.

jhodgdon’s picture

Status: Needs work » Needs review
StatusFileSize
new36.28 KB
new2.53 KB

Here's a new patch with the requested test for the empty section.

After looking at the screen shot of the verbose output of the test, I decided that the "nothing in the section" sentence should end with . -- it looked really weird without that. So, added that to HelpController, added a bit to HelpTest, and added a test plugin to the existing help_page_test test module.

jhodgdon’s picture

Assigned: jhodgdon » Unassigned

Reviews welcome. :)

larowlan’s picture

Status: Needs review » Reviewed & tested by the community

New test looks good

alexpott’s picture

Status: Reviewed & tested by the community » Fixed

Committed 1ac67bb and pushed to 8.1.x and 8.2.x. Thanks!

  • alexpott committed b511038 on 8.2.x
    Issue #2661200 by jhodgdon, Wim Leers, Cottser, Mile23, vijaycs85,...

  • alexpott committed 1ac67bb on 8.1.x
    Issue #2661200 by jhodgdon, Wim Leers, Cottser, Mile23, vijaycs85,...
jhodgdon’s picture

WOOT! Thanks all!!

wim leers’s picture

xjm’s picture

Issue tags: +8.1.0 release notes
jhodgdon’s picture

Thanks @Wim Leers. I was just going to check to see if the change record had been published. :)

Status: Fixed » Closed (fixed)

Automatically closed - issue fixed for 2 weeks with no activity.