Drupal 8.0.0 beta 12. More info: https://www.drupal.org/node/2514176
This commit is contained in:
commit
9921556621
13277 changed files with 1459781 additions and 0 deletions
61
core/modules/tour/config/schema/tour.schema.yml
Normal file
61
core/modules/tour/config/schema/tour.schema.yml
Normal file
|
@ -0,0 +1,61 @@
|
|||
# Schema for the configuration files of the Tour module.
|
||||
|
||||
tour.tour.*:
|
||||
type: config_entity
|
||||
label: 'Tour settings'
|
||||
mapping:
|
||||
id:
|
||||
type: string
|
||||
label: 'ID'
|
||||
label:
|
||||
type: label
|
||||
label: 'Label'
|
||||
module:
|
||||
type: string
|
||||
label: 'Providing module'
|
||||
routes:
|
||||
type: sequence
|
||||
label: 'Route settings'
|
||||
sequence:
|
||||
type: route
|
||||
label: 'Route'
|
||||
tips:
|
||||
type: sequence
|
||||
label: 'Tips'
|
||||
sequence:
|
||||
type: tour.tip.[plugin]
|
||||
label: 'Tour tip'
|
||||
|
||||
tour.tip:
|
||||
type: mapping
|
||||
label: 'Tour tip'
|
||||
mapping:
|
||||
id:
|
||||
type: string
|
||||
label: 'ID'
|
||||
plugin:
|
||||
type: string
|
||||
label: 'Plugin'
|
||||
label:
|
||||
type: label
|
||||
label: 'Label'
|
||||
weight:
|
||||
type: integer
|
||||
label: 'Weight'
|
||||
location:
|
||||
type: string
|
||||
label: 'Location'
|
||||
attributes:
|
||||
type: sequence
|
||||
label: 'Attributes'
|
||||
sequence:
|
||||
type: string
|
||||
label: 'Attribute'
|
||||
|
||||
tour.tip.text:
|
||||
type: tour.tip
|
||||
label: 'Textual tour tip'
|
||||
mapping:
|
||||
body:
|
||||
type: text
|
||||
label: 'Body'
|
142
core/modules/tour/css/tour.module.css
Normal file
142
core/modules/tour/css/tour.module.css
Normal file
|
@ -0,0 +1,142 @@
|
|||
/**
|
||||
* @file
|
||||
* Styling for tour module.
|
||||
*/
|
||||
|
||||
/* Tab appearance. */
|
||||
.toolbar .toolbar-bar .tour-toolbar-tab.toolbar-tab {
|
||||
float: right; /* LTR */
|
||||
}
|
||||
[dir="rtl"] .toolbar .toolbar-bar .tour-toolbar-tab.toolbar-tab {
|
||||
float: left;
|
||||
}
|
||||
|
||||
/* Override placement of the tour progress indicator. */
|
||||
.tour-progress {
|
||||
position: absolute;
|
||||
bottom: 20px;
|
||||
right: 20px; /* LTR */
|
||||
}
|
||||
[dir="rtl"] .tour-progress {
|
||||
right: auto;
|
||||
left: 20px;
|
||||
}
|
||||
|
||||
/* Default styles for the container */
|
||||
.joyride-tip-guide {
|
||||
position: absolute;
|
||||
display: none;
|
||||
background: #fff;
|
||||
width: 300px;
|
||||
z-index: 101;
|
||||
top: 0;
|
||||
left: 0;
|
||||
}
|
||||
@media only screen and (max-width: 767px) {
|
||||
.joyride-tip-guide {
|
||||
width: 85%;
|
||||
left: 2.5%;
|
||||
}
|
||||
}
|
||||
|
||||
.joyride-content-wrapper {
|
||||
position: relative;
|
||||
padding: 20px 50px 20px 20px; /* LTR */
|
||||
}
|
||||
[dir="rtl"] .joyride-content-wrapper {
|
||||
padding: 20px 20px 20px 50px;
|
||||
}
|
||||
|
||||
/* Add a little css triangle pip, older browser just miss out on the fanciness of it. */
|
||||
.joyride-tip-guide .joyride-nub {
|
||||
display: block;
|
||||
position: absolute;
|
||||
left: 22px;
|
||||
width: 0;
|
||||
height: 0;
|
||||
}
|
||||
|
||||
.joyride-tip-guide .joyride-nub.top {
|
||||
top: -28px;
|
||||
bottom: auto;
|
||||
}
|
||||
|
||||
.joyride-tip-guide .joyride-nub.bottom {
|
||||
bottom: -28px;
|
||||
}
|
||||
|
||||
.joyride-tip-guide .joyride-nub.right {
|
||||
top: 22px;
|
||||
bottom: auto;
|
||||
left: auto;
|
||||
right: -28px;
|
||||
}
|
||||
|
||||
.joyride-tip-guide .joyride-nub.left {
|
||||
top: 22px;
|
||||
left: -28px;
|
||||
right: auto;
|
||||
bottom: auto;
|
||||
}
|
||||
|
||||
.joyride-tip-guide .joyride-nub.top-right {
|
||||
top: -28px;
|
||||
bottom: auto;
|
||||
left: auto;
|
||||
right: 28px;
|
||||
}
|
||||
|
||||
.joyride-tip-guide .tour-tip-label {
|
||||
margin-top: 0;
|
||||
}
|
||||
|
||||
.joyride-tip-guide p {
|
||||
margin: 0 0 1.4em;
|
||||
}
|
||||
|
||||
.joyride-timer-indicator-wrap {
|
||||
width: 50px;
|
||||
height: 3px;
|
||||
position: absolute;
|
||||
right: 17px;
|
||||
bottom: 16px;
|
||||
}
|
||||
.joyride-timer-indicator {
|
||||
display: block;
|
||||
width: 0;
|
||||
height: inherit;
|
||||
}
|
||||
|
||||
.joyride-close-tip {
|
||||
position: absolute;
|
||||
line-height: 1em;
|
||||
right: 20px; /* LTR */
|
||||
top: 20px;
|
||||
}
|
||||
[dir="rtl"] .joyride-close-tip {
|
||||
left: 20px;
|
||||
right: auto;
|
||||
}
|
||||
|
||||
.joyride-modal-bg {
|
||||
position: fixed;
|
||||
height: 100%;
|
||||
width: 100%;
|
||||
z-index: 100;
|
||||
display: none;
|
||||
top: 0;
|
||||
left: 0;
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
.joyride-expose-wrapper {
|
||||
position: absolute;
|
||||
z-index: 102;
|
||||
}
|
||||
|
||||
.joyride-expose-cover {
|
||||
position: absolute;
|
||||
z-index: 10000;
|
||||
top: 0;
|
||||
left: 0;
|
||||
}
|
265
core/modules/tour/js/tour.js
Normal file
265
core/modules/tour/js/tour.js
Normal file
|
@ -0,0 +1,265 @@
|
|||
/**
|
||||
* @file
|
||||
* Attaches behaviors for the Tour module's toolbar tab.
|
||||
*/
|
||||
|
||||
(function ($, Backbone, Drupal, document) {
|
||||
|
||||
"use strict";
|
||||
|
||||
var queryString = decodeURI(window.location.search);
|
||||
|
||||
/**
|
||||
* Attaches the tour's toolbar tab behavior.
|
||||
*
|
||||
* It uses the query string for:
|
||||
* - tour: When ?tour=1 is present, the tour will start automatically
|
||||
* after the page has loaded.
|
||||
* - tips: Pass ?tips=class in the url to filter the available tips to
|
||||
* the subset which match the given class.
|
||||
*
|
||||
* @example
|
||||
* http://example.com/foo?tour=1&tips=bar
|
||||
*
|
||||
* @type {Drupal~behavior}
|
||||
*/
|
||||
Drupal.behaviors.tour = {
|
||||
attach: function (context) {
|
||||
$('body').once('tour').each(function () {
|
||||
var model = new Drupal.tour.models.StateModel();
|
||||
new Drupal.tour.views.ToggleTourView({
|
||||
el: $(context).find('#toolbar-tab-tour'),
|
||||
model: model
|
||||
});
|
||||
|
||||
model
|
||||
// Allow other scripts to respond to tour events.
|
||||
.on('change:isActive', function (model, isActive) {
|
||||
$(document).trigger((isActive) ? 'drupalTourStarted' : 'drupalTourStopped');
|
||||
})
|
||||
// Initialization: check whether a tour is available on the current
|
||||
// page.
|
||||
.set('tour', $(context).find('ol#tour'));
|
||||
|
||||
// Start the tour immediately if toggled via query string.
|
||||
if (/tour=?/i.test(queryString)) {
|
||||
model.set('isActive', true);
|
||||
}
|
||||
});
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* @namespace
|
||||
*/
|
||||
Drupal.tour = Drupal.tour || {
|
||||
|
||||
/**
|
||||
* @namespace Drupal.tour.models
|
||||
*/
|
||||
models: {},
|
||||
|
||||
/**
|
||||
* @namespace Drupal.tour.views
|
||||
*/
|
||||
views: {}
|
||||
};
|
||||
|
||||
/**
|
||||
* Backbone Model for tours.
|
||||
*
|
||||
* @constructor
|
||||
*
|
||||
* @augments Backbone.Model
|
||||
*/
|
||||
Drupal.tour.models.StateModel = Backbone.Model.extend(/** @lends Drupal.tour.models.StateModel# */{
|
||||
|
||||
/**
|
||||
* @type {object}
|
||||
*/
|
||||
defaults: /** @lends Drupal.tour.models.StateModel# */{
|
||||
|
||||
/**
|
||||
* Indicates whether the Drupal root window has a tour.
|
||||
*
|
||||
* @type {Array}
|
||||
*/
|
||||
tour: [],
|
||||
|
||||
/**
|
||||
* Indicates whether the tour is currently running.
|
||||
*
|
||||
* @type {bool}
|
||||
*/
|
||||
isActive: false,
|
||||
|
||||
/**
|
||||
* Indicates which tour is the active one (necessary to cleanly stop).
|
||||
*
|
||||
* @type {Array}
|
||||
*/
|
||||
activeTour: []
|
||||
}
|
||||
});
|
||||
|
||||
Drupal.tour.views.ToggleTourView = Backbone.View.extend(/** @lends Drupal.tour.views.ToggleTourView# */{
|
||||
|
||||
/**
|
||||
* @type {object}
|
||||
*/
|
||||
events: {'click': 'onClick'},
|
||||
|
||||
/**
|
||||
* Handles edit mode toggle interactions.
|
||||
*
|
||||
* @constructs
|
||||
*
|
||||
* @augments Backbone.View
|
||||
*/
|
||||
initialize: function () {
|
||||
this.listenTo(this.model, 'change:tour change:isActive', this.render);
|
||||
this.listenTo(this.model, 'change:isActive', this.toggleTour);
|
||||
},
|
||||
|
||||
/**
|
||||
* @inheritdoc
|
||||
*
|
||||
* @return {Drupal.tour.views.ToggleTourView}
|
||||
*/
|
||||
render: function () {
|
||||
// Render the visibility.
|
||||
this.$el.toggleClass('hidden', this._getTour().length === 0);
|
||||
// Render the state.
|
||||
var isActive = this.model.get('isActive');
|
||||
this.$el.find('button')
|
||||
.toggleClass('is-active', isActive)
|
||||
.prop('aria-pressed', isActive);
|
||||
return this;
|
||||
},
|
||||
|
||||
/**
|
||||
* Model change handler; starts or stops the tour.
|
||||
*/
|
||||
toggleTour: function () {
|
||||
if (this.model.get('isActive')) {
|
||||
var $tour = this._getTour();
|
||||
this._removeIrrelevantTourItems($tour, this._getDocument());
|
||||
var that = this;
|
||||
if ($tour.find('li').length) {
|
||||
$tour.joyride({
|
||||
autoStart: true,
|
||||
postRideCallback: function () { that.model.set('isActive', false); },
|
||||
// HTML segments for tip layout.
|
||||
template: {
|
||||
link: '<a href=\"#close\" class=\"joyride-close-tip\">×</a>',
|
||||
button: '<a href=\"#\" class=\"button button--primary joyride-next-tip\"></a>'
|
||||
}
|
||||
});
|
||||
this.model.set({isActive: true, activeTour: $tour});
|
||||
}
|
||||
}
|
||||
else {
|
||||
this.model.get('activeTour').joyride('destroy');
|
||||
this.model.set({isActive: false, activeTour: []});
|
||||
}
|
||||
},
|
||||
|
||||
/**
|
||||
* Toolbar tab click event handler; toggles isActive.
|
||||
*
|
||||
* @param {jQuery.Event} event
|
||||
*/
|
||||
onClick: function (event) {
|
||||
this.model.set('isActive', !this.model.get('isActive'));
|
||||
event.preventDefault();
|
||||
event.stopPropagation();
|
||||
},
|
||||
|
||||
/**
|
||||
* Gets the tour.
|
||||
*
|
||||
* @return {jQuery}
|
||||
* A jQuery element pointing to a `<ol>` containing tour items.
|
||||
*/
|
||||
_getTour: function () {
|
||||
return this.model.get('tour');
|
||||
},
|
||||
|
||||
/**
|
||||
* Gets the relevant document as a jQuery element.
|
||||
*
|
||||
* @return {jQuery}
|
||||
* A jQuery element pointing to the document within which a tour would be
|
||||
* started given the current state.
|
||||
*/
|
||||
_getDocument: function () {
|
||||
return $(document);
|
||||
},
|
||||
|
||||
/**
|
||||
* Removes tour items for elements that don't have matching page elements.
|
||||
*
|
||||
* Or that are explicitly filtered out via the 'tips' query string.
|
||||
*
|
||||
* @example
|
||||
* <caption>This will filter out tips that do not have a matching
|
||||
* page element or don't have the "bar" class.</caption>
|
||||
* http://example.com/foo?tips=bar
|
||||
*
|
||||
* @param {jQuery} $tour
|
||||
* A jQuery element pointing to a `<ol>` containing tour items.
|
||||
* @param {jQuery} $document
|
||||
* A jQuery element pointing to the document within which the elements
|
||||
* should be sought.
|
||||
*
|
||||
* @see Drupal.tour.views.ToggleTourView#_getDocument
|
||||
*/
|
||||
_removeIrrelevantTourItems: function ($tour, $document) {
|
||||
var removals = false;
|
||||
var tips = /tips=([^&]+)/.exec(queryString);
|
||||
$tour
|
||||
.find('li')
|
||||
.each(function () {
|
||||
var $this = $(this);
|
||||
var itemId = $this.attr('data-id');
|
||||
var itemClass = $this.attr('data-class');
|
||||
// If the query parameter 'tips' is set, remove all tips that don't
|
||||
// have the matching class.
|
||||
if (tips && !$(this).hasClass(tips[1])) {
|
||||
removals = true;
|
||||
$this.remove();
|
||||
return;
|
||||
}
|
||||
// Remove tip from the DOM if there is no corresponding page element.
|
||||
if ((!itemId && !itemClass) ||
|
||||
(itemId && $document.find('#' + itemId).length) ||
|
||||
(itemClass && $document.find('.' + itemClass).length)) {
|
||||
return;
|
||||
}
|
||||
removals = true;
|
||||
$this.remove();
|
||||
});
|
||||
|
||||
// If there were removals, we'll have to do some clean-up.
|
||||
if (removals) {
|
||||
var total = $tour.find('li').length;
|
||||
if (!total) {
|
||||
this.model.set({tour: []});
|
||||
}
|
||||
|
||||
$tour
|
||||
.find('li')
|
||||
// Rebuild the progress data.
|
||||
.each(function (index) {
|
||||
var progress = Drupal.t('!tour_item of !total', {'!tour_item': index + 1, '!total': total});
|
||||
$(this).find('.tour-progress').text(progress);
|
||||
})
|
||||
// Update the last item to have "End tour" as the button.
|
||||
.eq(-1)
|
||||
.attr('data-text', Drupal.t('End tour'));
|
||||
}
|
||||
}
|
||||
|
||||
});
|
||||
|
||||
})(jQuery, Backbone, Drupal, document);
|
44
core/modules/tour/src/Annotation/Tip.php
Normal file
44
core/modules/tour/src/Annotation/Tip.php
Normal file
|
@ -0,0 +1,44 @@
|
|||
<?php
|
||||
|
||||
/**
|
||||
* @file
|
||||
* Contains \Drupal\tour\Annotation\Tip.
|
||||
*/
|
||||
|
||||
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;
|
||||
|
||||
}
|
189
core/modules/tour/src/Entity/Tour.php
Normal file
189
core/modules/tour/src/Entity/Tour.php
Normal file
|
@ -0,0 +1,189 @@
|
|||
<?php
|
||||
|
||||
/**
|
||||
* @file
|
||||
* Contains \Drupal\tour\Entity\Tour.
|
||||
*/
|
||||
|
||||
namespace Drupal\tour\Entity;
|
||||
|
||||
use Drupal\Core\Config\Entity\ConfigEntityBase;
|
||||
use Drupal\Core\Entity\EntityStorageInterface;
|
||||
use Drupal\tour\TipsPluginCollection;
|
||||
use Drupal\tour\TourInterface;
|
||||
|
||||
/**
|
||||
* Defines the configured tour entity.
|
||||
*
|
||||
* @ConfigEntityType(
|
||||
* id = "tour",
|
||||
* label = @Translation("Tour"),
|
||||
* handlers = {
|
||||
* "view_builder" = "Drupal\tour\TourViewBuilder"
|
||||
* },
|
||||
* 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 = array();
|
||||
|
||||
/**
|
||||
* 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 = array();
|
||||
|
||||
/**
|
||||
* Overrides \Drupal\Core\Config\Entity\ConfigEntityBase::__construct();
|
||||
*/
|
||||
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 = array();
|
||||
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 = array();
|
||||
foreach ($this->getRoutes() as $route) {
|
||||
$this->keyedRoutes[$route['route_name']] = isset($route['route_params']) ? $route['route_params'] : array();
|
||||
}
|
||||
}
|
||||
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->dependencies;
|
||||
}
|
||||
|
||||
}
|
129
core/modules/tour/src/Plugin/tour/tip/TipPluginText.php
Normal file
129
core/modules/tour/src/Plugin/tour/tip/TipPluginText.php
Normal file
|
@ -0,0 +1,129 @@
|
|||
<?php
|
||||
|
||||
/**
|
||||
* @file
|
||||
* Contains \Drupal\tour\Plugin\tour\tip\TipPluginText.
|
||||
*/
|
||||
|
||||
namespace Drupal\tour\Plugin\tour\tip;
|
||||
|
||||
use Drupal\Component\Utility\Html;
|
||||
use Drupal\Component\Utility\SafeMarkup;
|
||||
use Drupal\Component\Utility\Xss;
|
||||
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;
|
||||
|
||||
/**
|
||||
* 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() {
|
||||
static $id;
|
||||
if (!isset($id)) {
|
||||
$id = Html::getUniqueId($this->get('id'));
|
||||
}
|
||||
return $id;
|
||||
}
|
||||
|
||||
/**
|
||||
* 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">' . SafeMarkup::checkPlain($this->getLabel()) . '</h2>';
|
||||
$output .= '<p class="tour-tip-body" id="tour-tip-' . $this->getAriaId() . '-contents">' . Xss::filterAdmin($this->token->replace($this->getBody())) . '</p>';
|
||||
return array('#markup' => $output);
|
||||
}
|
||||
|
||||
}
|
79
core/modules/tour/src/Tests/TourCacheTagsTest.php
Normal file
79
core/modules/tour/src/Tests/TourCacheTagsTest.php
Normal file
|
@ -0,0 +1,79 @@
|
|||
<?php
|
||||
|
||||
/**
|
||||
* @file
|
||||
* Contains \Drupal\tour\Tests\TourCacheTagsTest.
|
||||
*/
|
||||
|
||||
namespace Drupal\tour\Tests;
|
||||
|
||||
use Drupal\Core\Url;
|
||||
use Drupal\system\Tests\Cache\PageCacheTagsTestBase;
|
||||
use Drupal\tour\Entity\Tour;
|
||||
use Drupal\user\Entity\Role;
|
||||
use Drupal\user\RoleInterface;
|
||||
|
||||
/**
|
||||
* Tests the Tour entity's cache tags.
|
||||
*
|
||||
* @group tour
|
||||
*/
|
||||
class TourCacheTagsTest extends PageCacheTagsTestBase {
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public static $modules = array('tour', 'tour_test');
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
protected function setUp() {
|
||||
parent::setUp();
|
||||
|
||||
// Give anonymous users permission to view nodes, so that we can verify the
|
||||
// cache tags of cached versions of node pages.
|
||||
Role::load(RoleInterface::ANONYMOUS_ID)->grantPermission('access tour')
|
||||
->save();
|
||||
}
|
||||
|
||||
/**
|
||||
* Tests cache tags presence and invalidation of the Tour entity.
|
||||
*
|
||||
* Tests the following cache tags:
|
||||
* - 'tour:<tour ID>'
|
||||
*/
|
||||
public function testRenderedTour() {
|
||||
$url = Url::fromRoute('tour_test.1');
|
||||
|
||||
// Prime the page cache.
|
||||
$this->verifyPageCache($url, 'MISS');
|
||||
|
||||
// Verify a cache hit, but also the presence of the correct cache tags.
|
||||
$expected_tags = [
|
||||
'config:tour.tour.tour-test',
|
||||
'rendered',
|
||||
];
|
||||
$this->verifyPageCache($url, 'HIT', $expected_tags);
|
||||
|
||||
// Verify that after modifying the tour, there is a cache miss.
|
||||
$this->pass('Test modification of tour.', 'Debug');
|
||||
Tour::load('tour-test')->save();
|
||||
$this->verifyPageCache($url, 'MISS');
|
||||
|
||||
// Verify a cache hit.
|
||||
$this->verifyPageCache($url, 'HIT', $expected_tags);
|
||||
|
||||
// Verify that after deleting the tour, there is a cache miss.
|
||||
$this->pass('Test deletion of tour.', 'Debug');
|
||||
Tour::load('tour-test')->delete();
|
||||
$this->verifyPageCache($url, 'MISS');
|
||||
|
||||
// Verify a cache hit.
|
||||
$expected_tags = [
|
||||
'rendered',
|
||||
];
|
||||
$this->verifyPageCache($url, 'HIT', $expected_tags);
|
||||
}
|
||||
|
||||
}
|
47
core/modules/tour/src/Tests/TourPluginTest.php
Normal file
47
core/modules/tour/src/Tests/TourPluginTest.php
Normal file
|
@ -0,0 +1,47 @@
|
|||
<?php
|
||||
|
||||
/**
|
||||
* @file
|
||||
* Contains \Drupal\tour\Tests\TourPluginTest.
|
||||
*/
|
||||
|
||||
namespace Drupal\tour\Tests;
|
||||
|
||||
use Drupal\simpletest\KernelTestBase;
|
||||
|
||||
/**
|
||||
* Tests the functionality of tour plugins.
|
||||
*
|
||||
* @group tour
|
||||
*/
|
||||
class TourPluginTest extends KernelTestBase {
|
||||
|
||||
/**
|
||||
* Modules to enable.
|
||||
*
|
||||
* @var array
|
||||
*/
|
||||
public static $modules = array('tour');
|
||||
|
||||
/**
|
||||
* Stores the tour plugin manager.
|
||||
*
|
||||
* @var \Drupal\tour\TipPluginManager
|
||||
*/
|
||||
protected $pluginManager;
|
||||
|
||||
protected function setUp() {
|
||||
parent::setUp();
|
||||
|
||||
$this->installConfig(array('tour'));
|
||||
$this->pluginManager = $this->container->get('plugin.manager.tour.tip');
|
||||
}
|
||||
|
||||
/**
|
||||
* Test tour plugins.
|
||||
*/
|
||||
public function testTourPlugins() {
|
||||
$this->assertIdentical(count($this->pluginManager->getDefinitions()), 1, 'Only tour plugins for the enabled modules were returned.');
|
||||
}
|
||||
|
||||
}
|
179
core/modules/tour/src/Tests/TourTest.php
Normal file
179
core/modules/tour/src/Tests/TourTest.php
Normal file
|
@ -0,0 +1,179 @@
|
|||
<?php
|
||||
|
||||
/**
|
||||
* @file
|
||||
* Contains \Drupal\tour\Tests\TourTest.
|
||||
*/
|
||||
|
||||
namespace Drupal\tour\Tests;
|
||||
|
||||
use Drupal\language\Entity\ConfigurableLanguage;
|
||||
|
||||
/**
|
||||
* Tests the functionality of tour tips.
|
||||
*
|
||||
* @group tour
|
||||
*/
|
||||
class TourTest extends TourTestBasic {
|
||||
|
||||
/**
|
||||
* Modules to enable.
|
||||
*
|
||||
* @var array
|
||||
*/
|
||||
public static $modules = array('tour', 'locale', 'language', 'tour_test');
|
||||
|
||||
/**
|
||||
* The permissions required for a logged in user to test tour tips.
|
||||
*
|
||||
* @var array
|
||||
* A list of permissions.
|
||||
*/
|
||||
protected $permissions = array('access tour', 'administer languages');
|
||||
|
||||
/**
|
||||
* Tour tip attributes to be tested. Keyed by the path.
|
||||
*
|
||||
* @var array
|
||||
* An array of tip attributes, keyed by path.
|
||||
*/
|
||||
protected $tips = array(
|
||||
'tour-test-1' => array(),
|
||||
);
|
||||
|
||||
/**
|
||||
* Test tour functionality.
|
||||
*/
|
||||
public function testTourFunctionality() {
|
||||
// Navigate to tour-test-1 and verify the tour_test_1 tip is found with appropriate classes.
|
||||
$this->drupalGet('tour-test-1');
|
||||
|
||||
// Test the TourTestBase class assertTourTips() method.
|
||||
$tips = array();
|
||||
$tips[] = array('data-id' => 'tour-test-1');
|
||||
$tips[] = array('data-class' => 'tour-test-5');
|
||||
$this->assertTourTips($tips);
|
||||
$this->assertTourTips();
|
||||
|
||||
$elements = $this->xpath('//li[@data-id=:data_id and @class=:classes and ./p//a[@href=:href and contains(., :text)]]', array(
|
||||
':classes' => 'tip-module-tour-test tip-type-text tip-tour-test-1',
|
||||
':data_id' => 'tour-test-1',
|
||||
':href' => \Drupal::url('<front>', [], ['absolute' => TRUE]),
|
||||
':text' => 'Drupal',
|
||||
));
|
||||
$this->assertEqual(count($elements), 1, 'Found Token replacement.');
|
||||
|
||||
$elements = $this->cssSelect("li[data-id=tour-test-1] h2:contains('The first tip')");
|
||||
$this->assertEqual(count($elements), 1, 'Found English variant of tip 1.');
|
||||
|
||||
$elements = $this->cssSelect("li[data-id=tour-test-2] h2:contains('The quick brown fox')");
|
||||
$this->assertNotEqual(count($elements), 1, 'Did not find English variant of tip 2.');
|
||||
|
||||
$elements = $this->cssSelect("li[data-id=tour-test-1] h2:contains('La pioggia cade in spagna')");
|
||||
$this->assertNotEqual(count($elements), 1, 'Did not find Italian variant of tip 1.');
|
||||
|
||||
// Ensure that plugins work.
|
||||
$elements = $this->xpath('//img[@src="http://local/image.png"]');
|
||||
$this->assertEqual(count($elements), 1, 'Image plugin tip found.');
|
||||
|
||||
// Navigate to tour-test-2/subpath and verify the tour_test_2 tip is found.
|
||||
$this->drupalGet('tour-test-2/subpath');
|
||||
$elements = $this->cssSelect("li[data-id=tour-test-2] h2:contains('The quick brown fox')");
|
||||
$this->assertEqual(count($elements), 1, 'Found English variant of tip 2.');
|
||||
|
||||
$elements = $this->cssSelect("li[data-id=tour-test-1] h2:contains('The first tip')");
|
||||
$this->assertNotEqual(count($elements), 1, 'Did not find English variant of tip 1.');
|
||||
|
||||
// Enable Italian language and navigate to it/tour-test1 and verify italian
|
||||
// version of tip is found.
|
||||
ConfigurableLanguage::createFromLangcode('it')->save();
|
||||
$this->drupalGet('it/tour-test-1');
|
||||
|
||||
$elements = $this->cssSelect("li[data-id=tour-test-1] h2:contains('La pioggia cade in spagna')");
|
||||
$this->assertEqual(count($elements), 1, 'Found Italian variant of tip 1.');
|
||||
|
||||
$elements = $this->cssSelect("li[data-id=tour-test-2] h2:contains('The quick brown fox')");
|
||||
$this->assertNotEqual(count($elements), 1, 'Did not find English variant of tip 1.');
|
||||
|
||||
// Programmatically create a tour for use through the remainder of the test.
|
||||
$tour = entity_create('tour', array(
|
||||
'id' => 'tour-entity-create-test-en',
|
||||
'label' => 'Tour test english',
|
||||
'langcode' => 'en',
|
||||
'module' => 'system',
|
||||
'routes' => array(
|
||||
array('route_name' => 'tour_test.1'),
|
||||
),
|
||||
'tips' => array(
|
||||
'tour-test-1' => array(
|
||||
'id' => 'tour-code-test-1',
|
||||
'plugin' => 'text',
|
||||
'label' => 'The rain in spain',
|
||||
'body' => 'Falls mostly on the plain.',
|
||||
'weight' => '100',
|
||||
'attributes' => array(
|
||||
'data-id' => 'tour-code-test-1',
|
||||
),
|
||||
),
|
||||
'tour-code-test-2' => array(
|
||||
'id' => 'tour-code-test-2',
|
||||
'plugin' => 'image',
|
||||
'label' => 'The awesome image',
|
||||
'url' => 'http://local/image.png',
|
||||
'weight' => 1,
|
||||
'attributes' => array(
|
||||
'data-id' => 'tour-code-test-2'
|
||||
),
|
||||
),
|
||||
),
|
||||
));
|
||||
$tour->save();
|
||||
|
||||
// Ensure that a tour entity has the expected dependencies based on plugin
|
||||
// providers and the module named in the configuration entity.
|
||||
$dependencies = $tour->calculateDependencies();
|
||||
$this->assertEqual($dependencies['module'], array('system', 'tour_test'));
|
||||
|
||||
$this->drupalGet('tour-test-1');
|
||||
|
||||
// Load it back from the database and verify storage worked.
|
||||
$entity_save_tip = entity_load('tour', 'tour-entity-create-test-en');
|
||||
// Verify that hook_ENTITY_TYPE_load() integration worked.
|
||||
$this->assertEqual($entity_save_tip->loaded, 'Load hooks work');
|
||||
// Verify that hook_ENTITY_TYPE_presave() integration worked.
|
||||
$this->assertEqual($entity_save_tip->label(), 'Tour test english alter');
|
||||
|
||||
// Navigate to tour-test-1 and verify the new tip is found.
|
||||
$this->drupalGet('tour-test-1');
|
||||
$elements = $this->cssSelect("li[data-id=tour-code-test-1] h2:contains('The rain in spain')");
|
||||
$this->assertEqual(count($elements), 1, 'Found the required tip markup for tip 4');
|
||||
|
||||
// Verify that the weight sorting works by ensuring the lower weight item
|
||||
// (tip 4) has the 'End tour' button.
|
||||
$elements = $this->cssSelect("li[data-id=tour-code-test-1][data-text='End tour']");
|
||||
$this->assertEqual(count($elements), 1, 'Found code tip was weighted last and had "End tour".');
|
||||
|
||||
// Test hook_tour_alter().
|
||||
$this->assertText('Altered by hook_tour_tips_alter');
|
||||
|
||||
// Navigate to tour-test-3 and verify the tour_test_1 tip is found with
|
||||
// appropriate classes.
|
||||
$this->drupalGet('tour-test-3/foo');
|
||||
$elements = $this->xpath('//li[@data-id=:data_id and @class=:classes and ./h2[contains(., :text)]]', array(
|
||||
':classes' => 'tip-module-tour-test tip-type-text tip-tour-test-1',
|
||||
':data_id' => 'tour-test-1',
|
||||
':text' => 'The first tip',
|
||||
));
|
||||
$this->assertEqual(count($elements), 1, 'Found English variant of tip 1.');
|
||||
|
||||
// Navigate to tour-test-3 and verify the tour_test_1 tip is not found with
|
||||
// appropriate classes.
|
||||
$this->drupalGet('tour-test-3/bar');
|
||||
$elements = $this->xpath('//li[@data-id=:data_id and @class=:classes and ./h2[contains(., :text)]]', array(
|
||||
':classes' => 'tip-module-tour-test tip-type-text tip-tour-test-1',
|
||||
':data_id' => 'tour-test-1',
|
||||
':text' => 'The first tip',
|
||||
));
|
||||
$this->assertEqual(count($elements), 0, 'Did not find English variant of tip 1.');
|
||||
}
|
||||
}
|
75
core/modules/tour/src/Tests/TourTestBase.php
Normal file
75
core/modules/tour/src/Tests/TourTestBase.php
Normal file
|
@ -0,0 +1,75 @@
|
|||
<?php
|
||||
|
||||
/**
|
||||
* @file
|
||||
* Contains \Drupal\tour\Tests\TourTestBase.
|
||||
*/
|
||||
|
||||
namespace Drupal\tour\Tests;
|
||||
|
||||
use Drupal\simpletest\WebTestBase;
|
||||
|
||||
/**
|
||||
* Base class for testing Tour functionality.
|
||||
*/
|
||||
abstract class TourTestBase extends WebTestBase {
|
||||
|
||||
/**
|
||||
* Assert function to determine if tips rendered to the page
|
||||
* have a corresponding page element.
|
||||
*
|
||||
* @param array $tips
|
||||
* A list of tips which provide either a "data-id" or "data-class".
|
||||
*
|
||||
* @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
|
||||
*/
|
||||
public function assertTourTips($tips = array()) {
|
||||
// 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 = \PHPUnit_Util_XML::cssSelect('#' . $tip['data-id'], TRUE, $this->content, TRUE);
|
||||
$this->assertTrue(!empty($elements) && count($elements) === 1, format_string('Found corresponding page element for tour tip with id #%data-id', array('%data-id' => $tip['data-id'])));
|
||||
}
|
||||
else if (!empty($tip['data-class'])) {
|
||||
$elements = \PHPUnit_Util_XML::cssSelect('.' . $tip['data-class'], TRUE, $this->content, TRUE);
|
||||
$this->assertFalse(empty($elements), format_string('Found corresponding page element for tour tip with class .%data-class', array('%data-class' => $tip['data-class'])));
|
||||
}
|
||||
else {
|
||||
// It's a modal.
|
||||
$modals++;
|
||||
}
|
||||
$total++;
|
||||
}
|
||||
$this->pass(format_string('Total %total Tips tested of which %modals modal(s).', array('%total' => $total, '%modals' => $modals)));
|
||||
}
|
||||
}
|
||||
|
||||
}
|
75
core/modules/tour/src/Tests/TourTestBasic.php
Normal file
75
core/modules/tour/src/Tests/TourTestBasic.php
Normal file
|
@ -0,0 +1,75 @@
|
|||
<?php
|
||||
|
||||
/**
|
||||
* @file
|
||||
* Contains \Drupal\tour\Tests\TourTestBasic.
|
||||
*/
|
||||
|
||||
namespace Drupal\tour\Tests;
|
||||
|
||||
/**
|
||||
* Simple tour tips test base.
|
||||
*/
|
||||
abstract class TourTestBasic extends TourTestBase {
|
||||
|
||||
/**
|
||||
* Tour tip attributes to be tested. Keyed by the path.
|
||||
*
|
||||
* @var array
|
||||
* An array of tip attributes, keyed by path.
|
||||
*
|
||||
* @code
|
||||
* protected $tips = array(
|
||||
* '/foo/bar' => array(
|
||||
* array('data-id' => 'foo'),
|
||||
* array('data-class' => 'bar'),
|
||||
* ),
|
||||
* );
|
||||
* @endcode
|
||||
*/
|
||||
protected $tips = array();
|
||||
|
||||
/**
|
||||
* An admin user with administrative permissions for tour.
|
||||
*
|
||||
* @var \Drupal\user\UserInterface
|
||||
*/
|
||||
protected $adminUser;
|
||||
|
||||
/**
|
||||
* The permissions required for a logged in user to test tour tips.
|
||||
*
|
||||
* @var array
|
||||
* A list of permissions.
|
||||
*/
|
||||
protected $permissions = array('access tour');
|
||||
|
||||
protected function setUp() {
|
||||
parent::setUp();
|
||||
|
||||
// Make sure we are using distinct default and administrative themes for
|
||||
// the duration of these tests.
|
||||
$this->container->get('theme_handler')->install(array('bartik', 'seven'));
|
||||
$this->config('system.theme')
|
||||
->set('default', 'bartik')
|
||||
->set('admin', 'seven')
|
||||
->save();
|
||||
|
||||
$this->permissions[] = 'view the administration theme';
|
||||
|
||||
// Create an admin user to view tour tips.
|
||||
$this->adminUser = $this->drupalCreateUser($this->permissions);
|
||||
$this->drupalLogin($this->adminUser);
|
||||
}
|
||||
|
||||
/**
|
||||
* A simple tip test.
|
||||
*/
|
||||
public function testTips() {
|
||||
foreach ($this->tips as $path => $attributes) {
|
||||
$this->drupalGet($path);
|
||||
$this->assertTourTips($attributes);
|
||||
}
|
||||
}
|
||||
|
||||
}
|
87
core/modules/tour/src/TipPluginBase.php
Normal file
87
core/modules/tour/src/TipPluginBase.php
Normal file
|
@ -0,0 +1,87 @@
|
|||
<?php
|
||||
|
||||
/**
|
||||
* @file
|
||||
* Contains \Drupal\tour\TipPluginBase.
|
||||
*/
|
||||
|
||||
namespace Drupal\tour;
|
||||
|
||||
use Drupal\Core\Plugin\PluginBase;
|
||||
use Drupal\tour\TipPluginInterface;
|
||||
|
||||
/**
|
||||
* 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;
|
||||
}
|
||||
}
|
82
core/modules/tour/src/TipPluginInterface.php
Normal file
82
core/modules/tour/src/TipPluginInterface.php
Normal file
|
@ -0,0 +1,82 @@
|
|||
<?php
|
||||
|
||||
/**
|
||||
* @file
|
||||
* Contains \Drupal\tour\TipPluginInterface.
|
||||
*/
|
||||
|
||||
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();
|
||||
|
||||
}
|
42
core/modules/tour/src/TipPluginManager.php
Normal file
42
core/modules/tour/src/TipPluginManager.php
Normal file
|
@ -0,0 +1,42 @@
|
|||
<?php
|
||||
|
||||
/**
|
||||
* @file
|
||||
* Contains \Drupal\tour\TipPluginManager.
|
||||
*/
|
||||
|
||||
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');
|
||||
}
|
||||
|
||||
}
|
31
core/modules/tour/src/TipsPluginCollection.php
Normal file
31
core/modules/tour/src/TipsPluginCollection.php
Normal file
|
@ -0,0 +1,31 @@
|
|||
<?php
|
||||
|
||||
/**
|
||||
* @file
|
||||
* Contains \Drupal\tour\TipsPluginCollection.
|
||||
*/
|
||||
|
||||
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);
|
||||
}
|
||||
|
||||
}
|
70
core/modules/tour/src/TourInterface.php
Normal file
70
core/modules/tour/src/TourInterface.php
Normal file
|
@ -0,0 +1,70 @@
|
|||
<?php
|
||||
|
||||
/**
|
||||
* @file
|
||||
* Contains \Drupal\tour\TourInterface.
|
||||
*/
|
||||
|
||||
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();
|
||||
|
||||
}
|
81
core/modules/tour/src/TourViewBuilder.php
Normal file
81
core/modules/tour/src/TourViewBuilder.php
Normal file
|
@ -0,0 +1,81 @@
|
|||
<?php
|
||||
|
||||
/**
|
||||
* @file
|
||||
* Contains \Drupal\tour\TourViewBuilder.
|
||||
*/
|
||||
|
||||
namespace Drupal\tour;
|
||||
|
||||
use Drupal\Core\Entity\EntityViewBuilder;
|
||||
use Drupal\Core\Entity\EntityInterface;
|
||||
use Drupal\Component\Utility\Html;
|
||||
|
||||
/**
|
||||
* Provides a Tour view builder.
|
||||
*/
|
||||
class TourViewBuilder extends EntityViewBuilder {
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function viewMultiple(array $entities = array(), $view_mode = 'full', $langcode = NULL) {
|
||||
/** @var \Drupal\tour\TourInterface[] $entities */
|
||||
$build = array();
|
||||
foreach ($entities as $entity_id => $entity) {
|
||||
$tips = $entity->getTips();
|
||||
$count = count($tips);
|
||||
$list_items = array();
|
||||
foreach ($tips as $index => $tip) {
|
||||
if ($output = $tip->getOutput()) {
|
||||
$attributes = array(
|
||||
'class' => array(
|
||||
'tip-module-' . Html::cleanCssIdentifier($entity->getModule()),
|
||||
'tip-type-' . Html::cleanCssIdentifier($tip->getPluginId()),
|
||||
'tip-' . Html::cleanCssIdentifier($tip->id()),
|
||||
),
|
||||
);
|
||||
$list_items[] = array(
|
||||
'output' => $output,
|
||||
'counter' => array(
|
||||
'#type' => 'container',
|
||||
'#attributes' => array(
|
||||
'class' => array(
|
||||
'tour-progress',
|
||||
),
|
||||
),
|
||||
'#children' => t('!tour_item of !total', array('!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] = array(
|
||||
'#theme' => 'item_list',
|
||||
'#items' => $list_items,
|
||||
'#list_type' => 'ol',
|
||||
'#attributes' => array(
|
||||
'id' => 'tour',
|
||||
'class' => array(
|
||||
'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;
|
||||
}
|
||||
|
||||
}
|
142
core/modules/tour/tests/src/Unit/Entity/TourTest.php
Normal file
142
core/modules/tour/tests/src/Unit/Entity/TourTest.php
Normal file
|
@ -0,0 +1,142 @@
|
|||
<?php
|
||||
/**
|
||||
* @file
|
||||
* Contains \Drupal\Tests\tour\Unit\Entity\TourTest.
|
||||
*/
|
||||
|
||||
namespace Drupal\Tests\tour\Unit\Entity;
|
||||
|
||||
use Drupal\Tests\UnitTestCase;
|
||||
|
||||
/**
|
||||
* @coversDefaultClass \Drupal\tour\Entity\Tour
|
||||
* @group tour
|
||||
*/
|
||||
class TourTest extends UnitTestCase {
|
||||
|
||||
/**
|
||||
* Tests \Drupal\tour\Entity\Tour::hasMatchingRoute().
|
||||
*
|
||||
* @param array $routes
|
||||
* Array of routes as per the Tour::routes property.
|
||||
* @param string $route_name
|
||||
* The route name to match.
|
||||
* @param array $route_params
|
||||
* Array of route params.
|
||||
* @param bool $result
|
||||
* Expected result.
|
||||
*
|
||||
* @covers ::hasMatchingRoute
|
||||
*
|
||||
* @dataProvider routeProvider
|
||||
*/
|
||||
public function testHasMatchingRoute($routes, $route_name, $route_params, $result) {
|
||||
$tour = $this->getMockBuilder('\Drupal\tour\Entity\Tour')
|
||||
->disableOriginalConstructor()
|
||||
->setMethods(array('getRoutes'))
|
||||
->getMock();
|
||||
|
||||
$tour->expects($this->any())
|
||||
->method('getRoutes')
|
||||
->will($this->returnValue($routes));
|
||||
|
||||
$this->assertSame($result, $tour->hasMatchingRoute($route_name, $route_params));
|
||||
|
||||
$tour->resetKeyedRoutes();
|
||||
}
|
||||
|
||||
/*
|
||||
* Provides sample routes for testing.
|
||||
*/
|
||||
public function routeProvider() {
|
||||
return array(
|
||||
// Simple match.
|
||||
array(
|
||||
array(
|
||||
array('route_name' => 'some.route'),
|
||||
),
|
||||
'some.route',
|
||||
array(),
|
||||
TRUE,
|
||||
),
|
||||
// Simple non-match.
|
||||
array(
|
||||
array(
|
||||
array('route_name' => 'another.route'),
|
||||
),
|
||||
'some.route',
|
||||
array(),
|
||||
FALSE,
|
||||
),
|
||||
// Empty params.
|
||||
array(
|
||||
array(
|
||||
array(
|
||||
'route_name' => 'some.route',
|
||||
'route_params' => array('foo' => 'bar'),
|
||||
),
|
||||
),
|
||||
'some.route',
|
||||
array(),
|
||||
FALSE,
|
||||
),
|
||||
// Match on params.
|
||||
array(
|
||||
array(
|
||||
array(
|
||||
'route_name' => 'some.route',
|
||||
'route_params' => array('foo' => 'bar'),
|
||||
),
|
||||
),
|
||||
'some.route',
|
||||
array('foo' => 'bar'),
|
||||
TRUE,
|
||||
),
|
||||
// Non-matching params.
|
||||
array(
|
||||
array(
|
||||
array(
|
||||
'route_name' => 'some.route',
|
||||
'route_params' => array('foo' => 'bar'),
|
||||
),
|
||||
),
|
||||
'some.route',
|
||||
array('bar' => 'foo'),
|
||||
FALSE,
|
||||
),
|
||||
// One matching, one not.
|
||||
array(
|
||||
array(
|
||||
array(
|
||||
'route_name' => 'some.route',
|
||||
'route_params' => array('foo' => 'bar'),
|
||||
),
|
||||
array(
|
||||
'route_name' => 'some.route',
|
||||
'route_params' => array('bar' => 'foo'),
|
||||
),
|
||||
),
|
||||
'some.route',
|
||||
array('bar' => 'foo'),
|
||||
TRUE,
|
||||
),
|
||||
// One matching, one not.
|
||||
array(
|
||||
array(
|
||||
array(
|
||||
'route_name' => 'some.route',
|
||||
'route_params' => array('foo' => 'bar'),
|
||||
),
|
||||
array(
|
||||
'route_name' => 'some.route',
|
||||
'route_params' => array('foo' => 'baz'),
|
||||
),
|
||||
),
|
||||
'some.route',
|
||||
array('foo' => 'baz'),
|
||||
TRUE,
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
}
|
|
@ -0,0 +1,5 @@
|
|||
label: Tour test italian
|
||||
tips:
|
||||
tour-test-1:
|
||||
label: La pioggia cade in spagna
|
||||
body: Per lo più in pianura.
|
|
@ -0,0 +1,15 @@
|
|||
id: tour-test-2
|
||||
module: tour_test
|
||||
label: 'Tour test english'
|
||||
langcode: en
|
||||
routes:
|
||||
- route_name: tour_test.2
|
||||
tips:
|
||||
tour-test-2:
|
||||
id: tour-test-2
|
||||
plugin: text
|
||||
label: 'The quick brown fox'
|
||||
body: 'Per lo più in pianura.'
|
||||
weight: 2
|
||||
attributes:
|
||||
data-id: tour-test-2
|
|
@ -0,0 +1,40 @@
|
|||
id: tour-test
|
||||
module: tour_test
|
||||
label: 'Tour test english'
|
||||
langcode: en
|
||||
routes:
|
||||
- route_name: tour_test.1
|
||||
- route_name: tour_test.3
|
||||
route_params:
|
||||
locale: foo
|
||||
tips:
|
||||
tour-test-1:
|
||||
id: tour-test-1
|
||||
plugin: text
|
||||
label: 'The first tip'
|
||||
body: 'Is <a href="[site:url]">[site:name]</a> always the best dressed?'
|
||||
weight: 1
|
||||
attributes:
|
||||
data-id: tour-test-1
|
||||
tour-test-action:
|
||||
id: tour-test-3
|
||||
plugin: text
|
||||
label: 'The action'
|
||||
body: 'The action button of awesome'
|
||||
weight: 2
|
||||
attributes:
|
||||
data-class: button-action
|
||||
tour-test-3:
|
||||
id: tour-test-3
|
||||
plugin: image
|
||||
label: 'The awesome image'
|
||||
url: 'http://local/image.png'
|
||||
weight: 1
|
||||
tour-test-6:
|
||||
id: tour-test-6
|
||||
plugin: text
|
||||
label: 'Im a list'
|
||||
body: '<p>Im all these things:</p><ul><li>Modal</li><li>Awesome</li></ul>'
|
||||
weight: 6
|
||||
attributes:
|
||||
data-id: tour-test-3
|
|
@ -0,0 +1,9 @@
|
|||
# Schema for the configuration files of the Tour Test module.
|
||||
|
||||
tour.tip.image:
|
||||
type: tour.tip
|
||||
label: 'Image tour tip'
|
||||
mapping:
|
||||
url:
|
||||
type: uri
|
||||
label: 'Image URL'
|
|
@ -0,0 +1,79 @@
|
|||
<?php
|
||||
|
||||
/**
|
||||
* @file
|
||||
* Contains \Drupal\tour_test\Controller\TourTestController.
|
||||
*/
|
||||
|
||||
namespace Drupal\tour_test\Controller;
|
||||
|
||||
/**
|
||||
* Controller routines for tour_test routes.
|
||||
*/
|
||||
class TourTestController {
|
||||
|
||||
/**
|
||||
* Outputs some content for testing tours.
|
||||
*
|
||||
* @param string $locale
|
||||
* (optional) Dummy locale variable for testing routing parameters. Defaults
|
||||
* to 'foo'.
|
||||
*
|
||||
* @return array
|
||||
* Array of markup.
|
||||
*/
|
||||
public function tourTest1($locale = 'foo') {
|
||||
return array(
|
||||
'tip-1' => array(
|
||||
'#type' => 'container',
|
||||
'#attributes' => array(
|
||||
'id' => 'tour-test-1',
|
||||
),
|
||||
'#children' => t('Where does the rain in Spain fail?'),
|
||||
),
|
||||
'tip-3' => array(
|
||||
'#type' => 'container',
|
||||
'#attributes' => array(
|
||||
'id' => 'tour-test-3',
|
||||
),
|
||||
'#children' => t('Tip created now?'),
|
||||
),
|
||||
'tip-4' => array(
|
||||
'#type' => 'container',
|
||||
'#attributes' => array(
|
||||
'id' => 'tour-test-4',
|
||||
),
|
||||
'#children' => t('Tip created later?'),
|
||||
),
|
||||
'tip-5' => array(
|
||||
'#type' => 'container',
|
||||
'#attributes' => array(
|
||||
'class' => array('tour-test-5'),
|
||||
),
|
||||
'#children' => t('Tip created later?'),
|
||||
),
|
||||
'code-tip-1' => array(
|
||||
'#type' => 'container',
|
||||
'#attributes' => array(
|
||||
'id' => 'tour-code-test-1',
|
||||
),
|
||||
'#children' => t('Tip created now?'),
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Outputs some content for testing tours.
|
||||
*/
|
||||
public function tourTest2() {
|
||||
return array(
|
||||
'#type' => 'container',
|
||||
'#attributes' => array(
|
||||
'id' => 'tour-test-2',
|
||||
),
|
||||
'#children' => t('Pangram example'),
|
||||
);
|
||||
|
||||
}
|
||||
|
||||
}
|
|
@ -0,0 +1,54 @@
|
|||
<?php
|
||||
|
||||
/**
|
||||
* @file
|
||||
* Contains \Drupal\tour_test\Plugin\tour\tip\TipPluginImage.
|
||||
*/
|
||||
|
||||
namespace Drupal\tour_test\Plugin\tour\tip;
|
||||
|
||||
use Drupal\Component\Utility\SafeMarkup;
|
||||
use Drupal\tour\TipPluginBase;
|
||||
|
||||
/**
|
||||
* Displays an image as a tip.
|
||||
*
|
||||
* @Tip(
|
||||
* id = "image",
|
||||
* title = @Translation("Image")
|
||||
* )
|
||||
*/
|
||||
class TipPluginImage extends TipPluginBase {
|
||||
|
||||
/**
|
||||
* The url which is used for the image in this Tip.
|
||||
*
|
||||
* @var string
|
||||
* A url used for the image.
|
||||
*/
|
||||
protected $url;
|
||||
|
||||
/**
|
||||
* The alt text which is used for the image in this Tip.
|
||||
*
|
||||
* @var string
|
||||
* A alt text used for the image.
|
||||
*/
|
||||
protected $alt;
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function getOutput() {
|
||||
$prefix = '<h2 class="tour-tip-label" id="tour-tip-' . $this->get('ariaId') . '-label">' . SafeMarkup::checkPlain($this->get('label')) . '</h2>';
|
||||
$prefix .= '<p class="tour-tip-image" id="tour-tip-' . $this->get('ariaId') . '-contents">';
|
||||
return [
|
||||
'#prefix' => $prefix,
|
||||
'#theme' => 'image',
|
||||
'#uri' => $this->get('url'),
|
||||
'#alt' => $this->get('alt'),
|
||||
'#suffix' => '</p>',
|
||||
];
|
||||
}
|
||||
|
||||
}
|
8
core/modules/tour/tests/tour_test/tour_test.info.yml
Normal file
8
core/modules/tour/tests/tour_test/tour_test.info.yml
Normal file
|
@ -0,0 +1,8 @@
|
|||
name: Tour module tests
|
||||
type: module
|
||||
description: Tests module for tour module.
|
||||
package: Testing
|
||||
version: VERSION
|
||||
core: 8.x
|
||||
dependencies:
|
||||
- tour
|
|
@ -0,0 +1,5 @@
|
|||
tour_test_action:
|
||||
route_name: tour_test.1_action
|
||||
title: 'Tour test action'
|
||||
appears_on:
|
||||
- tour_test.1
|
37
core/modules/tour/tests/tour_test/tour_test.module
Normal file
37
core/modules/tour/tests/tour_test/tour_test.module
Normal file
|
@ -0,0 +1,37 @@
|
|||
<?php
|
||||
|
||||
/**
|
||||
* @file
|
||||
* Provides tests for tour module
|
||||
*/
|
||||
|
||||
use Drupal\Core\Entity\EntityInterface;
|
||||
|
||||
/**
|
||||
* Implements hook_ENTITY_TYPE_load() for tour.
|
||||
*/
|
||||
function tour_test_tour_load($entities) {
|
||||
if (isset($entities['tour-entity-create-test-en'])) {
|
||||
$entities['tour-entity-create-test-en']->loaded = 'Load hooks work';
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Implements hook_ENTITY_TYPE_presave() for tour.
|
||||
*/
|
||||
function tour_test_tour_presave($entity) {
|
||||
if ($entity->id() == 'tour-entity-create-test-en') {
|
||||
$entity->set('label', $entity->label() . ' alter');
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Implements hook_tour_tips_alter().
|
||||
*/
|
||||
function tour_test_tour_tips_alter(array &$tour_tips, EntityInterface $entity) {
|
||||
foreach ($tour_tips as $tour_tip) {
|
||||
if ($tour_tip->get('id') == 'tour-code-test-1') {
|
||||
$tour_tip->set('body', 'Altered by hook_tour_tips_alter');
|
||||
}
|
||||
}
|
||||
}
|
31
core/modules/tour/tests/tour_test/tour_test.routing.yml
Normal file
31
core/modules/tour/tests/tour_test/tour_test.routing.yml
Normal file
|
@ -0,0 +1,31 @@
|
|||
tour_test.1:
|
||||
path: '/tour-test-1'
|
||||
defaults:
|
||||
_controller: '\Drupal\tour_test\Controller\TourTestController::tourTest1'
|
||||
options:
|
||||
_admin_route: TRUE
|
||||
requirements:
|
||||
_access: 'TRUE'
|
||||
|
||||
tour_test.1_action:
|
||||
path: '/tour-test-1/action'
|
||||
defaults:
|
||||
_controller: '\Drupal\tour_test\Controller\TourTestController::tourTest1'
|
||||
requirements:
|
||||
_access: 'TRUE'
|
||||
|
||||
tour_test.2:
|
||||
path: '/tour-test-2/subpath'
|
||||
defaults:
|
||||
_controller: '\Drupal\tour_test\Controller\TourTestController::tourTest2'
|
||||
requirements:
|
||||
_access: 'TRUE'
|
||||
|
||||
tour_test.3:
|
||||
path: '/tour-test-3/{locale}'
|
||||
defaults:
|
||||
locale: 'foo'
|
||||
_controller: '\Drupal\tour_test\Controller\TourTestController::tourTest1'
|
||||
requirements:
|
||||
_access: 'TRUE'
|
||||
|
46
core/modules/tour/tour.api.php
Normal file
46
core/modules/tour/tour.api.php
Normal file
|
@ -0,0 +1,46 @@
|
|||
<?php
|
||||
|
||||
/**
|
||||
* @file
|
||||
* Describes API functions for tour module.
|
||||
*/
|
||||
|
||||
/**
|
||||
* @addtogroup hooks
|
||||
* @{
|
||||
*/
|
||||
|
||||
/**
|
||||
* Allow modules to alter tour items before render.
|
||||
*
|
||||
* @param array $tour_tips
|
||||
* Array of \Drupal\tour\TipPluginInterface items.
|
||||
* @param \Drupal\Core\Entity\EntityInterface $entity
|
||||
* The tour which contains the $tour_tips.
|
||||
*/
|
||||
function hook_tour_tips_alter(array &$tour_tips, Drupal\Core\Entity\EntityInterface $entity) {
|
||||
foreach ($tour_tips as $tour_tip) {
|
||||
if ($tour_tip->get('id') == 'tour-code-test-1') {
|
||||
$tour_tip->set('body', 'Altered by hook_tour_tips_alter');
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Allow modules to alter tip plugin definitions.
|
||||
*
|
||||
* @param array $info
|
||||
* The array of tip plugin definitions, keyed by plugin ID.
|
||||
*
|
||||
* @see \Drupal\tour\Annotation\Tip
|
||||
*/
|
||||
function hook_tour_tips_info_alter(&$info) {
|
||||
// Swap out the class used for this tip plugin.
|
||||
if (isset($info['text'])) {
|
||||
$info['class'] = 'Drupal\mymodule\Plugin\tour\tip\MyCustomTipPlugin';
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @} End of "addtogroup hooks".
|
||||
*/
|
6
core/modules/tour/tour.info.yml
Normal file
6
core/modules/tour/tour.info.yml
Normal file
|
@ -0,0 +1,6 @@
|
|||
name: Tour
|
||||
type: module
|
||||
description: Provides guided tours.
|
||||
package: Core
|
||||
version: VERSION
|
||||
core: 8.x
|
17
core/modules/tour/tour.libraries.yml
Normal file
17
core/modules/tour/tour.libraries.yml
Normal file
|
@ -0,0 +1,17 @@
|
|||
tour:
|
||||
version: VERSION
|
||||
js:
|
||||
js/tour.js: {}
|
||||
dependencies:
|
||||
- core/jquery
|
||||
- core/jquery.once
|
||||
- core/drupal
|
||||
- core/backbone
|
||||
- core/jquery.joyride
|
||||
- tour/tour-styling
|
||||
|
||||
tour-styling:
|
||||
version: VERSION
|
||||
css:
|
||||
component:
|
||||
css/tour.module.css: { media: screen }
|
114
core/modules/tour/tour.module
Normal file
114
core/modules/tour/tour.module
Normal file
|
@ -0,0 +1,114 @@
|
|||
<?php
|
||||
|
||||
/**
|
||||
* @file
|
||||
* Main functions of the module.
|
||||
*/
|
||||
|
||||
use Drupal\Core\Cache\CacheBackendInterface;
|
||||
use Drupal\Core\Routing\RouteMatchInterface;
|
||||
|
||||
/**
|
||||
* Implements hook_help().
|
||||
*/
|
||||
function tour_help($route_name, RouteMatchInterface $route_match) {
|
||||
switch ($route_name) {
|
||||
case 'help.page.tour':
|
||||
$output = '';
|
||||
$output .= '<h3>' . t('About') . '</h3>';
|
||||
$output .= '<p>' . 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 <a href='!tour'>the online documentation for the Tour module</a>.", array('!tour' => 'https://www.drupal.org/documentation/modules/tour')) . '</p>';
|
||||
$output .= '<h3>' . t('Uses') . '</h3>';
|
||||
$output .= '<dl>';
|
||||
$output .= '<dt>' . t('Viewing tours') . '</dt>';
|
||||
$output .= '<dd>' . t("If a tour is available on a page, a <em>Tour</em> 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 <em>Next</em> button in the tip. To see a tour users must have the permission <em>Access tour</em> and JavaScript must be enabled in the browser") . '</dd>';
|
||||
$output .= '<dt>' . t('Creating tours') . '</dt>';
|
||||
$output .= '<dd>' . t("Tours can be written as YAML-documents with a text editor, or using the contributed <a href='!tour_ui'>Tour UI</a> module. For more information, see <a href='!doc_url'>the online documentation for writing tours</a>.", array('!doc_url' => 'https://www.drupal.org/developing/api/tour', '!tour_ui' => 'https://www.drupal.org/project/tour_ui')) . '</dd>';
|
||||
$output .= '</dl>';
|
||||
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'] += array(
|
||||
'#type' => 'toolbar_item',
|
||||
'tab' => array(
|
||||
'#type' => 'html_tag',
|
||||
'#tag' => 'button',
|
||||
'#value' => t('Tour'),
|
||||
'#attributes' => array(
|
||||
'class' => array('toolbar-icon', 'toolbar-icon-help'),
|
||||
'role' => 'button',
|
||||
'aria-pressed' => 'false',
|
||||
),
|
||||
),
|
||||
'#wrapper_attributes' => array(
|
||||
'class' => array('tour-toolbar-tab', 'hidden'),
|
||||
'id' => 'toolbar-tab-tour',
|
||||
),
|
||||
'#attached' => array(
|
||||
'library' => array(
|
||||
'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 = entity_load_multiple('tour', 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'] = entity_view_multiple($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();
|
||||
}
|
3
core/modules/tour/tour.permissions.yml
Normal file
3
core/modules/tour/tour.permissions.yml
Normal file
|
@ -0,0 +1,3 @@
|
|||
access tour:
|
||||
title: 'Access tour'
|
||||
description: 'View tour tips.'
|
4
core/modules/tour/tour.services.yml
Normal file
4
core/modules/tour/tour.services.yml
Normal file
|
@ -0,0 +1,4 @@
|
|||
services:
|
||||
plugin.manager.tour.tip:
|
||||
class: Drupal\tour\TipPluginManager
|
||||
parent: default_plugin_manager
|
Reference in a new issue