' . t('About') . ''; $output .= '

' . t("The Tour module provides users with guided tours of the site interface. Each tour consists of several tips that highlight elements of the user interface, guide the user through a workflow, or explain key concepts of the website. For more information, see the online documentation for the Tour module.", [':tour' => 'https://www.drupal.org/documentation/modules/tour']) . '

'; $output .= '

' . t('Uses') . '

'; $output .= '
'; $output .= '
' . t('Viewing tours') . '
'; $output .= '
' . t("If a tour is available on a page, a Tour button will be visible in the toolbar. If you click this button the first tip of the tour will appear. The tour continues after clicking the Next button in the tip. To see a tour users must have the permission Access tour and JavaScript must be enabled in the browser") . '
'; $output .= '
' . t('Creating tours') . '
'; $output .= '
' . t("Tours can be written as YAML-documents with a text editor, or using the contributed Tour UI module. For more information, see the online documentation for writing tours.", [':doc_url' => 'https://www.drupal.org/developing/api/tour', ':tour_ui' => 'https://www.drupal.org/project/tour_ui']) . '
'; $output .= '
'; return $output; } } /** * Implements hook_toolbar(). */ function tour_toolbar() { $items = []; $items['tour'] = [ '#cache' => [ 'contexts' => [ 'user.permissions', ], ], ]; if (!\Drupal::currentUser()->hasPermission('access tour')) { return $items; } $items['tour'] += [ '#type' => 'toolbar_item', 'tab' => [ '#type' => 'html_tag', '#tag' => 'button', '#value' => t('Tour'), '#attributes' => [ 'class' => ['toolbar-icon', 'toolbar-icon-help'], 'aria-pressed' => 'false', 'type' => 'button', ], ], '#wrapper_attributes' => [ 'class' => ['tour-toolbar-tab', 'hidden'], 'id' => 'toolbar-tab-tour', ], '#attached' => [ 'library' => [ 'tour/tour', ], ], ]; return $items; } /** * Implements hook_page_bottom(). */ function tour_page_bottom(array &$page_bottom) { if (!\Drupal::currentUser()->hasPermission('access tour')) { return; } // Load all of the items and match on route name. $route_match = \Drupal::routeMatch(); $route_name = $route_match->getRouteName(); $results = \Drupal::entityQuery('tour') ->condition('routes.*.route_name', $route_name) ->execute(); if (!empty($results) && $tours = Tour::loadMultiple(array_keys($results))) { foreach ($tours as $id => $tour) { // Match on params. if (!$tour->hasMatchingRoute($route_name, $route_match->getRawParameters()->all())) { unset($tours[$id]); } } if (!empty($tours)) { $page_bottom['tour'] = \Drupal::entityTypeManager() ->getViewBuilder('tour') ->viewMultiple($tours, 'full'); } } } /** * Implements hook_ENTITY_TYPE_insert() for tour entities. */ function tour_tour_insert($entity) { \Drupal::service('plugin.manager.tour.tip')->clearCachedDefinitions(); } /** * Implements hook_ENTITY_TYPE_update() for tour entities. */ function tour_tour_update($entity) { \Drupal::service('plugin.manager.tour.tip')->clearCachedDefinitions(); } /** * Implements hook_ENTITY_TYPE_presave() for tour entities. * * @todo https://www.drupal.org/i/3195823 Remove once deprecated properties are * no longer supported. */ function tour_tour_presave(Tour $tour) { _tour_update_joyride($tour); } /** * Updates a tour to make it compatible with the Shepherd library. * * @param \Drupal\tour\Entity\Tour $tour * The tour to update. * @param bool $trigger_deprecation * (optional) Whether to trigger deprecations. Defaults to TRUE. * * @return bool * Whether or not the entity needs saving. * * @see tour_post_update_joyride_selectors_to_selector_property() * * @internal * * @todo https://www.drupal.org/i/3195823 Remove once deprecated properties are * no longer supported. */ function _tour_update_joyride(Tour $tour, bool $trigger_deprecation = TRUE): bool { $needs_save = FALSE; // Update jQuery Joyride based plugins into a new, more structured format that // is compatible with Shepherd. $tips = $tour->get('tips'); foreach ($tips as &$tip) { if (isset($tip['attributes']['data-class']) || isset($tip['attributes']['data-id'])) { if ($trigger_deprecation) { @trigger_error("The tour.tip 'attributes' config schema property is deprecated in drupal:9.2.0 and is removed from drupal:10.0.0. Instead of 'data-class' and 'data-id' attributes, use 'selector' to specify the element a tip attaches to. See https://www.drupal.org/node/3204093", E_USER_DEPRECATED); } $needs_save = TRUE; $selector = isset($tip['attributes']['data-class']) ? ".{$tip['attributes']['data-class']}" : NULL; $selector = isset($tip['attributes']['data-id']) ? "#{$tip['attributes']['data-id']}" : $selector; $tip['selector'] = $selector; // Although the attributes property is deprecated, only the properties // with 1:1 equivalents are unset. unset($tip['attributes']['data-class'], $tip['attributes']['data-id']); // Remove attributes if it is now empty. if (empty($tip['attributes'])) { unset($tip['attributes']); } } if (isset($tip['location'])) { if ($trigger_deprecation) { @trigger_error("The tour.tip 'location' config schema property is deprecated in drupal:9.2.0 and is removed from drupal:10.0.0. Instead use 'position'. The value must be a valid placement accepted by PopperJS. See https://www.drupal.org/node/3204093", E_USER_DEPRECATED); } $needs_save = TRUE; // Joyride only supports four location options: 'top', 'bottom', // 'left', and 'right'. Shepherd also accepts these as options, but they // result in different behavior. A given Joyride location option will // provide the same results in Shepherd if '-start' is appended to it ( // e.g. the 'left-start' option in Shepherd positions the element the // same way that 'left' does in Joyride. // // @see https://shepherdjs.dev/docs/Step.html $tip['position'] = $tip['location'] . '-start'; unset($tip['location']); } } if ($needs_save) { $tour->set('tips', $tips); } return $needs_save; }