first commit
This commit is contained in:
@@ -0,0 +1,39 @@
|
||||
<?php
|
||||
|
||||
namespace Drupal\tour\Annotation;
|
||||
|
||||
use Drupal\Component\Annotation\Plugin;
|
||||
|
||||
/**
|
||||
* Defines a tour item annotation object.
|
||||
*
|
||||
* Plugin Namespace: Plugin\tour\tip
|
||||
*
|
||||
* For a working example, see \Drupal\tour\Plugin\tour\tip\TipPluginText
|
||||
*
|
||||
* @see \Drupal\tour\TipPluginBase
|
||||
* @see \Drupal\tour\TipPluginInterface
|
||||
* @see \Drupal\tour\TipPluginManager
|
||||
* @see plugin_api
|
||||
*
|
||||
* @Annotation
|
||||
*/
|
||||
class Tip extends Plugin {
|
||||
|
||||
/**
|
||||
* The plugin ID.
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
public $id;
|
||||
|
||||
/**
|
||||
* The title of the plugin.
|
||||
*
|
||||
* @var \Drupal\Core\Annotation\Translation
|
||||
*
|
||||
* @ingroup plugin_translatable
|
||||
*/
|
||||
public $title;
|
||||
|
||||
}
|
||||
@@ -0,0 +1,192 @@
|
||||
<?php
|
||||
|
||||
namespace Drupal\tour\Entity;
|
||||
|
||||
use Drupal\Core\Config\Entity\ConfigEntityBase;
|
||||
use Drupal\tour\TipsPluginCollection;
|
||||
use Drupal\tour\TourInterface;
|
||||
|
||||
/**
|
||||
* Defines the configured tour entity.
|
||||
*
|
||||
* @ConfigEntityType(
|
||||
* id = "tour",
|
||||
* label = @Translation("Tour"),
|
||||
* label_collection = @Translation("Tours"),
|
||||
* label_singular = @Translation("tour"),
|
||||
* label_plural = @Translation("tours"),
|
||||
* label_count = @PluralTranslation(
|
||||
* singular = "@count tour",
|
||||
* plural = "@count tours",
|
||||
* ),
|
||||
* handlers = {
|
||||
* "view_builder" = "Drupal\tour\TourViewBuilder",
|
||||
* "access" = "Drupal\tour\TourAccessControlHandler",
|
||||
* },
|
||||
* admin_permission = "administer site configuration",
|
||||
* entity_keys = {
|
||||
* "id" = "id",
|
||||
* "label" = "label"
|
||||
* },
|
||||
* config_export = {
|
||||
* "id",
|
||||
* "label",
|
||||
* "module",
|
||||
* "routes",
|
||||
* "tips",
|
||||
* },
|
||||
* lookup_keys = {
|
||||
* "routes.*.route_name"
|
||||
* }
|
||||
* )
|
||||
*/
|
||||
class Tour extends ConfigEntityBase implements TourInterface {
|
||||
|
||||
/**
|
||||
* The name (plugin ID) of the tour.
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
protected $id;
|
||||
|
||||
/**
|
||||
* The module which this tour is assigned to.
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
protected $module;
|
||||
|
||||
/**
|
||||
* The label of the tour.
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
protected $label;
|
||||
|
||||
/**
|
||||
* The routes on which this tour should be displayed.
|
||||
*
|
||||
* @var array
|
||||
*/
|
||||
protected $routes = [];
|
||||
|
||||
/**
|
||||
* The routes on which this tour should be displayed, keyed by route id.
|
||||
*
|
||||
* @var array
|
||||
*/
|
||||
protected $keyedRoutes;
|
||||
|
||||
/**
|
||||
* Holds the collection of tips that are attached to this tour.
|
||||
*
|
||||
* @var \Drupal\tour\TipsPluginCollection
|
||||
*/
|
||||
protected $tipsCollection;
|
||||
|
||||
/**
|
||||
* The array of plugin config, only used for export and to populate the $tipsCollection.
|
||||
*
|
||||
* @var array
|
||||
*/
|
||||
protected $tips = [];
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function __construct(array $values, $entity_type) {
|
||||
parent::__construct($values, $entity_type);
|
||||
|
||||
$this->tipsCollection = new TipsPluginCollection(\Drupal::service('plugin.manager.tour.tip'), $this->tips);
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function getRoutes() {
|
||||
return $this->routes;
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function getTip($id) {
|
||||
return $this->tipsCollection->get($id);
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function getTips() {
|
||||
$tips = [];
|
||||
foreach ($this->tips as $id => $tip) {
|
||||
$tips[] = $this->getTip($id);
|
||||
}
|
||||
uasort($tips, function ($a, $b) {
|
||||
if ($a->getWeight() == $b->getWeight()) {
|
||||
return 0;
|
||||
}
|
||||
return ($a->getWeight() < $b->getWeight()) ? -1 : 1;
|
||||
});
|
||||
|
||||
\Drupal::moduleHandler()->alter('tour_tips', $tips, $this);
|
||||
return array_values($tips);
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function getModule() {
|
||||
return $this->module;
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function hasMatchingRoute($route_name, $route_params) {
|
||||
if (!isset($this->keyedRoutes)) {
|
||||
$this->keyedRoutes = [];
|
||||
foreach ($this->getRoutes() as $route) {
|
||||
$this->keyedRoutes[$route['route_name']] = isset($route['route_params']) ? $route['route_params'] : [];
|
||||
}
|
||||
}
|
||||
if (!isset($this->keyedRoutes[$route_name])) {
|
||||
// We don't know about this route.
|
||||
return FALSE;
|
||||
}
|
||||
if (empty($this->keyedRoutes[$route_name])) {
|
||||
// We don't need to worry about route params, the route name is enough.
|
||||
return TRUE;
|
||||
}
|
||||
foreach ($this->keyedRoutes[$route_name] as $key => $value) {
|
||||
// If a required param is missing or doesn't match, return FALSE.
|
||||
if (empty($route_params[$key]) || $route_params[$key] !== $value) {
|
||||
return FALSE;
|
||||
}
|
||||
}
|
||||
return TRUE;
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function resetKeyedRoutes() {
|
||||
unset($this->keyedRoutes);
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function calculateDependencies() {
|
||||
parent::calculateDependencies();
|
||||
|
||||
foreach ($this->tipsCollection as $instance) {
|
||||
$definition = $instance->getPluginDefinition();
|
||||
$this->addDependency('module', $definition['provider']);
|
||||
}
|
||||
|
||||
$this->addDependency('module', $this->module);
|
||||
return $this;
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,133 @@
|
||||
<?php
|
||||
|
||||
namespace Drupal\tour\Plugin\HelpSection;
|
||||
|
||||
use Drupal\Core\Entity\EntityTypeManagerInterface;
|
||||
use Drupal\Core\Link;
|
||||
use Drupal\Core\Plugin\ContainerFactoryPluginInterface;
|
||||
use Drupal\Core\Url;
|
||||
use Drupal\help\Plugin\HelpSection\HelpSectionPluginBase;
|
||||
use Symfony\Component\DependencyInjection\ContainerInterface;
|
||||
|
||||
/**
|
||||
* Provides the tours list section for the help page.
|
||||
*
|
||||
* @HelpSection(
|
||||
* id = "tour",
|
||||
* title = @Translation("Tours"),
|
||||
* weight = 10,
|
||||
* description = @Translation("Tours guide you through workflows or explain concepts on various user interface pages. 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). Available tours:"),
|
||||
* permission = "access tour"
|
||||
* )
|
||||
*/
|
||||
class TourHelpSection extends HelpSectionPluginBase implements ContainerFactoryPluginInterface {
|
||||
|
||||
/**
|
||||
* The entity type manager.
|
||||
*
|
||||
* @var \Drupal\Core\Entity\EntityTypeManagerInterface
|
||||
*/
|
||||
protected $entityTypeManager;
|
||||
|
||||
/**
|
||||
* Constructs a TourHelpSection object.
|
||||
*
|
||||
* @param array $configuration
|
||||
* A configuration array containing information about the plugin instance.
|
||||
* @param string $plugin_id
|
||||
* The plugin_id for the plugin instance.
|
||||
* @param mixed $plugin_definition
|
||||
* The plugin implementation definition.
|
||||
* @param \Drupal\Core\Entity\EntityTypeManagerInterface $entity_type_manager
|
||||
* The entity manager service.
|
||||
*/
|
||||
public function __construct(array $configuration, $plugin_id, $plugin_definition, EntityTypeManagerInterface $entity_type_manager) {
|
||||
parent::__construct($configuration, $plugin_id, $plugin_definition);
|
||||
$this->entityTypeManager = $entity_type_manager;
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public static function create(ContainerInterface $container, array $configuration, $plugin_id, $plugin_definition) {
|
||||
return new static(
|
||||
$configuration,
|
||||
$plugin_id,
|
||||
$plugin_definition,
|
||||
$container->get('entity_type.manager')
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
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. Rather than doing a complicated
|
||||
// accounting of the cache metadata for all of these possible factors, set
|
||||
// the max age of the cache to zero to prevent using incorrect cached
|
||||
// information.
|
||||
return 0;
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function listTopics() {
|
||||
/** @var \Drupal\tour\TourInterface[] $tours */
|
||||
$tours = $this->entityTypeManager->getStorage('tour')->loadMultiple();
|
||||
// Sort in the manner defined by Tour.
|
||||
uasort($tours, ['Drupal\tour\Entity\Tour', 'sort']);
|
||||
|
||||
// Make a link to each tour, using the first of its routes that can
|
||||
// be linked to by this user, if any.
|
||||
$topics = [];
|
||||
foreach ($tours as $tour) {
|
||||
$title = $tour->label();
|
||||
$id = $tour->id();
|
||||
$routes = $tour->getRoutes();
|
||||
$made_link = FALSE;
|
||||
foreach ($routes as $route) {
|
||||
// Some tours are for routes with parameters. For instance, there is
|
||||
// currently a tour in the Language module for the language edit page,
|
||||
// which appears on all pages with URLs like:
|
||||
// /admin/config/regional/language/edit/LANGCODE.
|
||||
// There is no way to make a link to the page that displays the tour,
|
||||
// because it is a set of pages. The easiest way to detect this is to
|
||||
// use a try/catch exception -- try to make a link, and it will error
|
||||
// out with a missing parameter exception if the route leads to a set
|
||||
// of pages instead of a single page.
|
||||
try {
|
||||
$params = isset($route['route_params']) ? $route['route_params'] : [];
|
||||
$url = Url::fromRoute($route['route_name'], $params);
|
||||
// Skip this route if the current user cannot access it.
|
||||
if (!$url->access()) {
|
||||
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;
|
||||
break;
|
||||
}
|
||||
catch (\Exception $e) {
|
||||
// Exceptions are normally due to routes that need parameters. If
|
||||
// there is an exception, just try the next route and see if we can
|
||||
// find one that will work for us.
|
||||
}
|
||||
}
|
||||
if (!$made_link) {
|
||||
// None of the routes worked to make a link, so at least display the
|
||||
// tour title.
|
||||
$topics[$id] = $title;
|
||||
}
|
||||
}
|
||||
|
||||
return $topics;
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,128 @@
|
||||
<?php
|
||||
|
||||
namespace Drupal\tour\Plugin\tour\tip;
|
||||
|
||||
use Drupal\Component\Utility\Html;
|
||||
use Drupal\Core\Plugin\ContainerFactoryPluginInterface;
|
||||
use Drupal\Core\Utility\Token;
|
||||
use Drupal\tour\TipPluginBase;
|
||||
use Symfony\Component\DependencyInjection\ContainerInterface;
|
||||
|
||||
/**
|
||||
* Displays some text as a tip.
|
||||
*
|
||||
* @Tip(
|
||||
* id = "text",
|
||||
* title = @Translation("Text")
|
||||
* )
|
||||
*/
|
||||
class TipPluginText extends TipPluginBase implements ContainerFactoryPluginInterface {
|
||||
|
||||
/**
|
||||
* The body text which is used for render of this Text Tip.
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
protected $body;
|
||||
|
||||
/**
|
||||
* Token service.
|
||||
*
|
||||
* @var \Drupal\Core\Utility\Token
|
||||
*/
|
||||
protected $token;
|
||||
|
||||
/**
|
||||
* The forced position of where the tip will be located.
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
protected $location;
|
||||
|
||||
/**
|
||||
* Unique aria-id.
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
protected $ariaId;
|
||||
|
||||
/**
|
||||
* Constructs a \Drupal\tour\Plugin\tour\tip\TipPluginText object.
|
||||
*
|
||||
* @param array $configuration
|
||||
* A configuration array containing information about the plugin instance.
|
||||
* @param string $plugin_id
|
||||
* The plugin_id for the plugin instance.
|
||||
* @param mixed $plugin_definition
|
||||
* The plugin implementation definition.
|
||||
* @param \Drupal\Core\Utility\Token $token
|
||||
* The token service.
|
||||
*/
|
||||
public function __construct(array $configuration, $plugin_id, $plugin_definition, Token $token) {
|
||||
parent::__construct($configuration, $plugin_id, $plugin_definition);
|
||||
$this->token = $token;
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public static function create(ContainerInterface $container, array $configuration, $plugin_id, $plugin_definition) {
|
||||
return new static($configuration, $plugin_id, $plugin_definition, $container->get('token'));
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns a ID that is guaranteed uniqueness.
|
||||
*
|
||||
* @return string
|
||||
* A unique id to be used to generate aria attributes.
|
||||
*/
|
||||
public function getAriaId() {
|
||||
if (!$this->ariaId) {
|
||||
$this->ariaId = Html::getUniqueId($this->get('id'));
|
||||
}
|
||||
return $this->ariaId;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns body of the text tip.
|
||||
*
|
||||
* @return string
|
||||
* The tip body.
|
||||
*/
|
||||
public function getBody() {
|
||||
return $this->get('body');
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns location of the text tip.
|
||||
*
|
||||
* @return string
|
||||
* The tip location.
|
||||
*/
|
||||
public function getLocation() {
|
||||
return $this->get('location');
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function getAttributes() {
|
||||
$attributes = parent::getAttributes();
|
||||
$attributes['data-aria-describedby'] = 'tour-tip-' . $this->getAriaId() . '-contents';
|
||||
$attributes['data-aria-labelledby'] = 'tour-tip-' . $this->getAriaId() . '-label';
|
||||
if ($location = $this->get('location')) {
|
||||
$attributes['data-options'] = 'tipLocation:' . $location;
|
||||
}
|
||||
return $attributes;
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function getOutput() {
|
||||
$output = '<h2 class="tour-tip-label" id="tour-tip-' . $this->getAriaId() . '-label">' . Html::escape($this->getLabel()) . '</h2>';
|
||||
$output .= '<p class="tour-tip-body" id="tour-tip-' . $this->getAriaId() . '-contents">' . $this->token->replace($this->getBody()) . '</p>';
|
||||
return ['#markup' => $output];
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,76 @@
|
||||
<?php
|
||||
|
||||
namespace Drupal\tour\Tests;
|
||||
|
||||
use Drupal\Component\Render\FormattableMarkup;
|
||||
use Drupal\simpletest\WebTestBase;
|
||||
|
||||
@trigger_error('\Drupal\tour\Tests\TourTestBase is deprecated in 8.4.0 and will be removed before Drupal 9.0.0. Instead, use \Drupal\Tests\tour\Functional\TourTestBase.', E_USER_DEPRECATED);
|
||||
|
||||
/**
|
||||
* Base class for testing Tour functionality.
|
||||
*
|
||||
* @deprecated in drupal:8.4.0 and is removed from drupal:9.0.0.
|
||||
* Use \Drupal\Tests\tour\Functional\TourTestBase instead.
|
||||
*/
|
||||
abstract class TourTestBase extends WebTestBase {
|
||||
|
||||
/**
|
||||
* Assert function to determine if tips rendered to the page
|
||||
* have a corresponding page element.
|
||||
*
|
||||
* @code
|
||||
* // Basic example.
|
||||
* $this->assertTourTips();
|
||||
*
|
||||
* // Advanced example. The following would be used for multipage or
|
||||
* // targeting a specific subset of tips.
|
||||
* $tips = array();
|
||||
* $tips[] = array('data-id' => 'foo');
|
||||
* $tips[] = array('data-id' => 'bar');
|
||||
* $tips[] = array('data-class' => 'baz');
|
||||
* $this->assertTourTips($tips);
|
||||
* @endcode
|
||||
*
|
||||
* @param array $tips
|
||||
* A list of tips which provide either a "data-id" or "data-class".
|
||||
*/
|
||||
public function assertTourTips($tips = []) {
|
||||
// Get the rendered tips and their data-id and data-class attributes.
|
||||
if (empty($tips)) {
|
||||
// Tips are rendered as <li> elements inside <ol id="tour">.
|
||||
$rendered_tips = $this->xpath('//ol[@id = "tour"]//li[starts-with(@class, "tip")]');
|
||||
foreach ($rendered_tips as $rendered_tip) {
|
||||
$attributes = (array) $rendered_tip->attributes();
|
||||
$tips[] = $attributes['@attributes'];
|
||||
}
|
||||
}
|
||||
|
||||
// If the tips are still empty we need to fail.
|
||||
if (empty($tips)) {
|
||||
$this->fail('Could not find tour tips on the current page.');
|
||||
}
|
||||
else {
|
||||
// Check for corresponding page elements.
|
||||
$total = 0;
|
||||
$modals = 0;
|
||||
foreach ($tips as $tip) {
|
||||
if (!empty($tip['data-id'])) {
|
||||
$elements = $this->xpath('//*[@id="' . $tip['data-id'] . '"]');
|
||||
$this->assertTrue(!empty($elements) && count($elements) === 1, new FormattableMarkup('Found corresponding page element for tour tip with id #%data-id', ['%data-id' => $tip['data-id']]));
|
||||
}
|
||||
elseif (!empty($tip['data-class'])) {
|
||||
$elements = $this->xpath('//*[contain(@class, "' . $tip['data-id'] . '")]');
|
||||
$this->assertFalse(empty($elements), new FormattableMarkup('Found corresponding page element for tour tip with class .%data-class', ['%data-class' => $tip['data-class']]));
|
||||
}
|
||||
else {
|
||||
// It's a modal.
|
||||
$modals++;
|
||||
}
|
||||
$total++;
|
||||
}
|
||||
$this->pass(new FormattableMarkup('Total %total Tips tested of which %modals modal(s).', ['%total' => $total, '%modals' => $modals]));
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,82 @@
|
||||
<?php
|
||||
|
||||
namespace Drupal\tour;
|
||||
|
||||
use Drupal\Core\Plugin\PluginBase;
|
||||
|
||||
/**
|
||||
* Defines a base tour item implementation.
|
||||
*
|
||||
* @see \Drupal\tour\Annotation\Tip
|
||||
* @see \Drupal\tour\TipPluginInterface
|
||||
* @see \Drupal\tour\TipPluginManager
|
||||
* @see plugin_api
|
||||
*/
|
||||
abstract class TipPluginBase extends PluginBase implements TipPluginInterface {
|
||||
|
||||
/**
|
||||
* The label which is used for render of this tip.
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
protected $label;
|
||||
|
||||
/**
|
||||
* Allows tips to take more priority that others.
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
protected $weight;
|
||||
|
||||
/**
|
||||
* The attributes that will be applied to the markup of this tip.
|
||||
*
|
||||
* @var array
|
||||
*/
|
||||
protected $attributes;
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function id() {
|
||||
return $this->get('id');
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function getLabel() {
|
||||
return $this->get('label');
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function getWeight() {
|
||||
return $this->get('weight');
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function getAttributes() {
|
||||
return $this->get('attributes') ?: [];
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function get($key) {
|
||||
if (!empty($this->configuration[$key])) {
|
||||
return $this->configuration[$key];
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function set($key, $value) {
|
||||
$this->configuration[$key] = $value;
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,77 @@
|
||||
<?php
|
||||
|
||||
namespace Drupal\tour;
|
||||
|
||||
/**
|
||||
* Defines an interface for tour items.
|
||||
*
|
||||
* @see \Drupal\tour\Annotation\Tip
|
||||
* @see \Drupal\tour\TipPluginBase
|
||||
* @see \Drupal\tour\TipPluginManager
|
||||
* @see plugin_api
|
||||
*/
|
||||
interface TipPluginInterface {
|
||||
|
||||
/**
|
||||
* Returns id of the tip.
|
||||
*
|
||||
* @return string
|
||||
* The id of the tip.
|
||||
*/
|
||||
public function id();
|
||||
|
||||
/**
|
||||
* Returns label of the tip.
|
||||
*
|
||||
* @return string
|
||||
* The label of the tip.
|
||||
*/
|
||||
public function getLabel();
|
||||
|
||||
/**
|
||||
* Returns weight of the tip.
|
||||
*
|
||||
* @return string
|
||||
* The weight of the tip.
|
||||
*/
|
||||
public function getWeight();
|
||||
|
||||
/**
|
||||
* Returns an array of attributes for the tip wrapper.
|
||||
*
|
||||
* @return array
|
||||
* An array of classes and values.
|
||||
*/
|
||||
public function getAttributes();
|
||||
|
||||
/**
|
||||
* Used for returning values by key.
|
||||
*
|
||||
* @var string
|
||||
* Key of the value.
|
||||
*
|
||||
* @return string
|
||||
* Value of the key.
|
||||
*/
|
||||
public function get($key);
|
||||
|
||||
/**
|
||||
* Used for returning values by key.
|
||||
*
|
||||
* @var string
|
||||
* Key of the value.
|
||||
*
|
||||
* @var string
|
||||
* Value of the key.
|
||||
*/
|
||||
public function set($key, $value);
|
||||
|
||||
/**
|
||||
* Returns a renderable array.
|
||||
*
|
||||
* @return array
|
||||
* A renderable array.
|
||||
*/
|
||||
public function getOutput();
|
||||
|
||||
}
|
||||
@@ -0,0 +1,37 @@
|
||||
<?php
|
||||
|
||||
namespace Drupal\tour;
|
||||
|
||||
use Drupal\Core\Cache\CacheBackendInterface;
|
||||
use Drupal\Core\Extension\ModuleHandlerInterface;
|
||||
use Drupal\Core\Plugin\DefaultPluginManager;
|
||||
|
||||
/**
|
||||
* Provides a plugin manager for tour items.
|
||||
*
|
||||
* @see \Drupal\tour\Annotation\Tip
|
||||
* @see \Drupal\tour\TipPluginBase
|
||||
* @see \Drupal\tour\TipPluginInterface
|
||||
* @see plugin_api
|
||||
*/
|
||||
class TipPluginManager extends DefaultPluginManager {
|
||||
|
||||
/**
|
||||
* Constructs a new TipPluginManager.
|
||||
*
|
||||
* @param \Traversable $namespaces
|
||||
* An object that implements \Traversable which contains the root paths
|
||||
* keyed by the corresponding namespace to look for plugin implementations,
|
||||
* @param \Drupal\Core\Cache\CacheBackendInterface $cache_backend
|
||||
* Cache backend instance to use.
|
||||
* @param \Drupal\Core\Extension\ModuleHandlerInterface $module_handler
|
||||
* The module handler to invoke the alter hook with.
|
||||
*/
|
||||
public function __construct(\Traversable $namespaces, CacheBackendInterface $cache_backend, ModuleHandlerInterface $module_handler) {
|
||||
parent::__construct('Plugin/tour/tip', $namespaces, $module_handler, 'Drupal\tour\TipPluginInterface', 'Drupal\tour\Annotation\Tip');
|
||||
|
||||
$this->alterInfo('tour_tips_info');
|
||||
$this->setCacheBackend($cache_backend, 'tour_plugins');
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,26 @@
|
||||
<?php
|
||||
|
||||
namespace Drupal\tour;
|
||||
|
||||
use Drupal\Core\Plugin\DefaultLazyPluginCollection;
|
||||
|
||||
/**
|
||||
* A collection of tips.
|
||||
*/
|
||||
class TipsPluginCollection extends DefaultLazyPluginCollection {
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
protected $pluginKey = 'plugin';
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*
|
||||
* @return \Drupal\tour\TipPluginInterface
|
||||
*/
|
||||
public function &get($instance_id) {
|
||||
return parent::get($instance_id);
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,28 @@
|
||||
<?php
|
||||
|
||||
namespace Drupal\tour;
|
||||
|
||||
use Drupal\Core\Access\AccessResult;
|
||||
use Drupal\Core\Entity\EntityAccessControlHandler;
|
||||
use Drupal\Core\Entity\EntityInterface;
|
||||
use Drupal\Core\Session\AccountInterface;
|
||||
|
||||
/**
|
||||
* Defines the access control handler for the tour entity type.
|
||||
*
|
||||
* @see \Drupal\tour\Entity\Tour
|
||||
*/
|
||||
class TourAccessControlHandler extends EntityAccessControlHandler {
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
protected function checkAccess(EntityInterface $entity, $operation, AccountInterface $account) {
|
||||
if ($operation === 'view') {
|
||||
return AccessResult::allowedIfHasPermissions($account, ['access tour', 'administer site configuration'], 'OR');
|
||||
}
|
||||
|
||||
return parent::checkAccess($entity, $operation, $account);
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,65 @@
|
||||
<?php
|
||||
|
||||
namespace Drupal\tour;
|
||||
|
||||
use Drupal\Core\Config\Entity\ConfigEntityInterface;
|
||||
|
||||
/**
|
||||
* Provides an interface defining a tour entity.
|
||||
*/
|
||||
interface TourInterface extends ConfigEntityInterface {
|
||||
|
||||
/**
|
||||
* The routes that this tour will appear on.
|
||||
*
|
||||
* @return array
|
||||
* Returns array of routes for the tour.
|
||||
*/
|
||||
public function getRoutes();
|
||||
|
||||
/**
|
||||
* Whether the tour matches a given set of route parameters.
|
||||
*
|
||||
* @param string $route_name
|
||||
* The route name the parameters are for.
|
||||
* @param array $route_params
|
||||
* Associative array of raw route params.
|
||||
*
|
||||
* @return bool
|
||||
* TRUE if the tour matches the route parameters.
|
||||
*/
|
||||
public function hasMatchingRoute($route_name, $route_params);
|
||||
|
||||
/**
|
||||
* Returns tip plugin.
|
||||
*
|
||||
* @param string $id
|
||||
* The identifier of the tip.
|
||||
*
|
||||
* @return \Drupal\tour\TipPluginInterface
|
||||
* The tip plugin.
|
||||
*/
|
||||
public function getTip($id);
|
||||
|
||||
/**
|
||||
* Returns the tips for this tour.
|
||||
*
|
||||
* @return array
|
||||
* An array of tip plugins.
|
||||
*/
|
||||
public function getTips();
|
||||
|
||||
/**
|
||||
* Gets the module this tour belongs to.
|
||||
*
|
||||
* @return string
|
||||
* The module this tour belongs to.
|
||||
*/
|
||||
public function getModule();
|
||||
|
||||
/**
|
||||
* Resets the statically cached keyed routes.
|
||||
*/
|
||||
public function resetKeyedRoutes();
|
||||
|
||||
}
|
||||
@@ -0,0 +1,75 @@
|
||||
<?php
|
||||
|
||||
namespace Drupal\tour;
|
||||
|
||||
use Drupal\Core\Entity\EntityViewBuilder;
|
||||
use Drupal\Component\Utility\Html;
|
||||
|
||||
/**
|
||||
* Provides a Tour view builder.
|
||||
*/
|
||||
class TourViewBuilder extends EntityViewBuilder {
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function viewMultiple(array $entities = [], $view_mode = 'full', $langcode = NULL) {
|
||||
/** @var \Drupal\tour\TourInterface[] $entities */
|
||||
$build = [];
|
||||
foreach ($entities as $entity_id => $entity) {
|
||||
$tips = $entity->getTips();
|
||||
$count = count($tips);
|
||||
$list_items = [];
|
||||
foreach ($tips as $index => $tip) {
|
||||
if ($output = $tip->getOutput()) {
|
||||
$attributes = [
|
||||
'class' => [
|
||||
'tip-module-' . Html::cleanCssIdentifier($entity->getModule()),
|
||||
'tip-type-' . Html::cleanCssIdentifier($tip->getPluginId()),
|
||||
'tip-' . Html::cleanCssIdentifier($tip->id()),
|
||||
],
|
||||
];
|
||||
$list_items[] = [
|
||||
'output' => $output,
|
||||
'counter' => [
|
||||
'#type' => 'container',
|
||||
'#attributes' => [
|
||||
'class' => [
|
||||
'tour-progress',
|
||||
],
|
||||
],
|
||||
'#children' => t('@tour_item of @total', ['@tour_item' => $index + 1, '@total' => $count]),
|
||||
],
|
||||
'#wrapper_attributes' => $tip->getAttributes() + $attributes,
|
||||
];
|
||||
}
|
||||
}
|
||||
// If there is at least one tour item, build the tour.
|
||||
if ($list_items) {
|
||||
end($list_items);
|
||||
$key = key($list_items);
|
||||
$list_items[$key]['#wrapper_attributes']['data-text'] = t('End tour');
|
||||
$build[$entity_id] = [
|
||||
'#theme' => 'item_list',
|
||||
'#items' => $list_items,
|
||||
'#list_type' => 'ol',
|
||||
'#attributes' => [
|
||||
'id' => 'tour',
|
||||
'class' => [
|
||||
'hidden',
|
||||
],
|
||||
],
|
||||
'#cache' => [
|
||||
'tags' => $entity->getCacheTags(),
|
||||
],
|
||||
];
|
||||
}
|
||||
}
|
||||
// If at least one tour was built, attach the tour library.
|
||||
if ($build) {
|
||||
$build['#attached']['library'][] = 'tour/tour';
|
||||
}
|
||||
return $build;
|
||||
}
|
||||
|
||||
}
|
||||
Reference in New Issue
Block a user