diff --git a/protected/humhub/docs/CHANGELOG.md b/protected/humhub/docs/CHANGELOG.md index 4104c9c4fa..b28b0cbafd 100644 --- a/protected/humhub/docs/CHANGELOG.md +++ b/protected/humhub/docs/CHANGELOG.md @@ -11,7 +11,8 @@ HumHub Change Log - Fix: #3552 `humhub\modules\user\authclient\AuthAction:auth()` not compatible with `yii\authclient\AuthAction:auth()` - Fix: #3545 OEmbed fetch limit ignored - Enh: Added `humhub\libs\RestrictedCallException` - +- Chng: Switched from [bootstrap-tour](https://github.com/sorich87/bootstrap-tour) to [bootstrap-tourist](https://github.com/IGreatlyDislikeJavascript/bootstrap-tourist) due to incompatibility to bootstrap v3.4.1 +- Enh: Added `humhub.modules.tour` module for handling tour logic 1.3.12 (March 26, 2019) --------------------------- diff --git a/protected/humhub/models/UrlOembed.php b/protected/humhub/models/UrlOembed.php index 99da29da41..c20f2db1db 100644 --- a/protected/humhub/models/UrlOembed.php +++ b/protected/humhub/models/UrlOembed.php @@ -176,7 +176,7 @@ class UrlOembed extends ActiveRecord } try { - if (!static::findExistingOembed($url)) { + if (!self::findExistingOembed($url)) { static::loadUrl($url); } } catch(RestrictedCallException $re) { diff --git a/protected/humhub/modules/stream/tests/codeception/acceptance.suite.yml b/protected/humhub/modules/stream/tests/codeception/acceptance.suite.yml index 9fdd29247e..ac654c6b92 100644 --- a/protected/humhub/modules/stream/tests/codeception/acceptance.suite.yml +++ b/protected/humhub/modules/stream/tests/codeception/acceptance.suite.yml @@ -18,4 +18,5 @@ modules: WebDriver: url: 'http://localhost:8080/' browser: chrome + window_size: maximize port: 4444 \ No newline at end of file diff --git a/protected/humhub/modules/tour/assets/TourAsset.php b/protected/humhub/modules/tour/assets/TourAsset.php new file mode 100644 index 0000000000..15944d045f --- /dev/null +++ b/protected/humhub/modules/tour/assets/TourAsset.php @@ -0,0 +1,58 @@ + true]; + + /** + * @inheritdoc + */ + public $js = [ + 'js/bootstrap-tourist.min.js', + 'js/humhub.tour.js' + ]; + + public $css = [ + 'css/bootstrap-tourist.min.css' + ]; + + /** + * @param View $view + * @return AssetBundle + */ + public static function register($view) + { + $view->registerJsConfig('tour', [ + 'dashboardUrl' => Url::to(['/dashboard/dashboard']), + 'completedUrl' => Url::to(['/tour/tour/tour-completed']), + 'template' => '' + ]); + return parent::register($view); + } +} + diff --git a/protected/humhub/modules/tour/controllers/TourController.php b/protected/humhub/modules/tour/controllers/TourController.php index eb9403c26a..f53f71df82 100644 --- a/protected/humhub/modules/tour/controllers/TourController.php +++ b/protected/humhub/modules/tour/controllers/TourController.php @@ -8,6 +8,7 @@ namespace humhub\modules\tour\controllers; +use humhub\modules\space\models\Membership; use Yii; use yii\web\HttpException; use humhub\modules\space\models\Space; @@ -22,16 +23,10 @@ use humhub\modules\space\models\Space; */ class TourController extends \humhub\components\Controller { - - /** - * @inheritdoc - */ - public function behaviors() + public function getAccessRules() { return [ - 'acl' => [ - 'class' => \humhub\components\behaviors\AccessControl::class, - ] + ['login'] ]; } @@ -42,19 +37,16 @@ class TourController extends \humhub\components\Controller { // get section parameter from completed tour - $section = Yii::$app->request->get('section'); + $section = Yii::$app->request->post('section'); - if (!in_array($section, Yii::$app->params['tour']['acceptableNames'])) + if (!in_array($section, Yii::$app->params['tour']['acceptableNames'])) { return; + } // set tour status to seen for current user Yii::$app->getModule('tour')->settings->user()->set($section, 1); } - /* - * Update user settings for hiding tour panel on dashboard - */ - public function actionHidePanel() { // set tour status to seen for current user @@ -62,7 +54,10 @@ class TourController extends \humhub\components\Controller } /** - * This is a special case, because we need to find a space to start the tour + * This is a special case, because we need to find a space to start the tour + * + * @return \yii\web\Response + * @throws HttpException */ public function actionStartSpaceTour() { @@ -70,7 +65,7 @@ class TourController extends \humhub\components\Controller $space = null; // Loop over all spaces where the user is member - foreach (\humhub\modules\space\models\Membership::getUserSpaces() as $space) { + foreach (Membership::getUserSpaces() as $space) { if ($space->isAdmin() && !$space->isArchived()) { // If user is admin on this space, it´s the perfect match break; diff --git a/protected/humhub/modules/tour/resources/css/bootstrap-tourist.css b/protected/humhub/modules/tour/resources/css/bootstrap-tourist.css new file mode 100644 index 0000000000..27658b5a31 --- /dev/null +++ b/protected/humhub/modules/tour/resources/css/bootstrap-tourist.css @@ -0,0 +1,66 @@ +/* ======================================================================== + * Bootstrap Tourist v0.7 + * Copyright FFS 2019 + * @ IGreatlyDislikeJavascript on Github + * + * bootstrap-tour - v0.11.0 + * http://bootstraptour.com + * ======================================================================== + * Copyright 2012-2015 Ulrich Sossou + * + * ======================================================================== + * Licensed under the MIT License (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://opensource.org/licenses/MIT + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * ======================================================================== + */ + +.tour-backdrop { + position: absolute; + z-index: 1100; + background-color: #000; + opacity: 0.8; + filter: alpha(opacity=80); +} +.tour-prevent { + position: absolute; + z-index: 1102; + background-color: #ccc; + opacity: 0.20; + filter: alpha(opacity=20); +} +.popover[class*="tour-"] { + z-index: 1110; +} +.popover[class*="tour-"] .popover-navigation { + padding: 9px 14px; + overflow: hidden; +} +.popover[class*="tour-"] .popover-navigation *[data-role="end"] { + float: right; +} +.popover[class*="tour-"] .popover-navigation *[data-role="prev"], +.popover[class*="tour-"] .popover-navigation *[data-role="next"], +.popover[class*="tour-"] .popover-navigation *[data-role="end"] { + cursor: pointer; +} +.popover[class*="tour-"] .popover-navigation *[data-role="prev"].disabled, +.popover[class*="tour-"] .popover-navigation *[data-role="next"].disabled, +.popover[class*="tour-"] .popover-navigation *[data-role="end"].disabled { + cursor: default; +} +.popover[class*="tour-"].orphan { + position: fixed; + margin-top: 0; +} +.popover[class*="tour-"].orphan .arrow { + display: none; +} \ No newline at end of file diff --git a/protected/humhub/modules/tour/resources/css/bootstrap-tourist.min.css b/protected/humhub/modules/tour/resources/css/bootstrap-tourist.min.css new file mode 100644 index 0000000000..932d283cf1 --- /dev/null +++ b/protected/humhub/modules/tour/resources/css/bootstrap-tourist.min.css @@ -0,0 +1 @@ +.tour-backdrop{position:absolute;z-index:1100;background-color:#000;opacity:.8;filter:alpha(opacity=80)}.tour-prevent{position:absolute;z-index:1102;background-color:#ccc;opacity:.20;filter:alpha(opacity=20)}.popover[class*="tour-"]{z-index:1110}.popover[class*="tour-"] .popover-navigation{padding:9px 14px;overflow:hidden}.popover[class*="tour-"] .popover-navigation *[data-role="end"]{float:right}.popover[class*="tour-"] .popover-navigation *[data-role="prev"],.popover[class*="tour-"] .popover-navigation *[data-role="next"],.popover[class*="tour-"] .popover-navigation *[data-role="end"]{cursor:pointer}.popover[class*="tour-"] .popover-navigation *[data-role="prev"].disabled,.popover[class*="tour-"] .popover-navigation *[data-role="next"].disabled,.popover[class*="tour-"] .popover-navigation *[data-role="end"].disabled{cursor:default}.popover[class*="tour-"].orphan{position:fixed;margin-top:0}.popover[class*="tour-"].orphan .arrow{display:none} \ No newline at end of file diff --git a/protected/humhub/modules/tour/resources/js/bootstrap-tourist.js b/protected/humhub/modules/tour/resources/js/bootstrap-tourist.js new file mode 100644 index 0000000000..67f4f40165 --- /dev/null +++ b/protected/humhub/modules/tour/resources/js/bootstrap-tourist.js @@ -0,0 +1,2293 @@ +/* ======================================================================== + * + * Bootstrap Tourist v0.10 + * Copyright FFS 2019 + * @ IGreatlyDislikeJavascript on Github + * + * This code is a fork of bootstrap-tour, with a lot of extra features + * and fixes. You can read about why this fork exists here: + * + * https://github.com/sorich87/bootstrap-tour/issues/713 + * + * The entire purpose of this fork is to start rewriting bootstrap-tour + * into native ES6 instead of the original coffeescript, and to implement + * the features and fixes requested in the github repo. Ideally this fork + * will then be taken back into the main repo and become part of + * bootstrap-tour again - this is not a fork to create a new plugin! + * + * I'm not a JS coder, so suggest you test very carefully and read the + * docs (comments) below before using. + * + * ======================================================================== + * ENTIRELY BASED UPON: + * + * bootstrap-tour - v0.11.0 + * http://bootstraptour.com + * ======================================================================== + * Copyright 2012-2015 Ulrich Sossou + * + * ======================================================================== + * Licensed under the MIT License (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://opensource.org/licenses/MIT + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * ======================================================================== + * + * Updated for CS by FFS 2018 - v0.10 + * + * Changes from 0.9: + * - smartPlacement option removed, deprecated + * - default params compatibility for IE + * - auto progress bar was killed in changes 0.7 -> 0.8 due to Bootstrap sanitizer, this is readded + * - major change to manipulation of BS4 popper.js for orphan steps + * - change to implementation of backdrop + * + * Changes from 0.8: + * - The fast fix in v0.7 didn't work for Bootstrap 4. This release is to ensure fully working popovers in BS4. Issue is that the Bootstrap CDN + * doesn't actually have the whitelist property, so developing against it is basically useless :( + * - Improved BS4 support and template switching. Changed options for framework vs template. + * + * Changes from 0.7: + * - Fast release to fix breaking change in Bootstrap 3.4.1, fixes this issue: https://github.com/sorich87/bootstrap-tour/issues/723#issuecomment-471107788 + * Issue is caused by the BS sanitizer, to avoid this reoccurring the "sanitizeWhitelist:" and "sanitizeFunction:" global options added + * + * Changes from 0.6: + * - Fixed invalid call to debug in _showNextStep() + * - Added onPreviouslyEnded() callback: https://github.com/sorich87/bootstrap-tour/issues/720 + * - Added selector to switch between bootstrap3 and bootstrap4 or custom template, thanks to: https://github.com/sorich87/bootstrap-tour/pull/643 + * + * Changes from 0.5: + * - Added "unfix" for bootstrap selectpicker to revert zindex after step that includes this plugin + * - Fixed issue with Bootstrap dialogs. Handling of dialogs is now robust + * - Fixed issue with BootstrapDialog plugin: https://nakupanda.github.io/bootstrap3-dialog/ . See notes below for help. + * - Improved the background overlay and scroll handling, unnecessary work removed + + + --------- + + + This fork and code adds following features to Bootstrap Tour + + 1. onNext/onPrevious - prevent auto-move to next step, allow .goTo + 2. *** Do not call Tour.init *** - fixed tours with hidden elements on page reload + 3. Dynamically determine step element by function + 4. Only continue tour when reflex element is clicked using reflexOnly + 5. Call onElementUnavailable if step element is missing + 6. Scroll flicker/continual step reload fixed + 7. Magic progress bar and progress text, plus options to customize per step + 8. Prevent user interaction with element using preventInteraction + 9. Wait for arbitrary DOM element to be visible before showing tour step/crapping out due to missing element, using delayOnElement + 10. Handle bootstrap modal dialogs better - autodetect modals or children of modals, and call onModalHidden to handle when user dismisses modal without following tour steps + 11. Automagically fixes drawing issues with Bootstrap Selectpicker (https://github.com/snapappointments/bootstrap-select/) + 12. Call onPreviouslyEnded if tour.start() is called for a tour that has previously ended (see docs) + 13. Switch between Bootstrap 3 or 4 (popover methods and template) automatically using tour options + 14. Added sanitizeWhitelist and sanitizeFunction global options + + -------------- + 1. Control flow from onNext() / onPrevious() options: + Returning false from onNext/onPrevious handler will prevent Tour from automatically moving to the next/previous step. + Tour flow methods (Tour.goTo etc) now also work correctly in onNext/onPrevious. + Option is available per step or globally: + + var tourSteps = [ + { + element: "#inputBanana", + title: "Bananas!", + content: "Bananas are yellow, except when they're not", + onNext: function(tour){ + if($('#inputBanana').val() !== "banana") + { + // no banana? highlight the banana field + $('#inputBanana').css("background-color", "red"); + // do not jump to the next tour step! + return false; + } + } + } + ]; + + var Tour=new Tour({ + steps: tourSteps, + framework: "bootstrap3", // or "bootstrap4" depending on your version of bootstrap + onNext: function(tour) + { + if(someVar = true) + { + // force the tour to jump to slide 3 + tour.goTo(3); + // Prevent default move to next step - important! + return false; + } + } + }); + + -------------- + 2. Do not call Tour.init + When setting up Tour, do not call Tour.init(). + Call Tour.start() to start/resume the Tour from previous step + Call Tour.restart() to always start Tour from first step + + Tour.init() was a redundant method that caused conflict with hidden Tour elements. + +--------------- + 3. Dynamically determine element by function + Step "element:" option allows element to be determined programmatically. Return a jquery object. + The following is possible: + + var tourSteps = [ + { + element: function() { return $(document).find("...something..."); }, + title: "Dynamic", + content: "Element found by function" + }, + { + element: "#static", + title: "Static", + content: "Element found by static ID" + } + ]; + +--------------- + 4. Only continue tour when reflex element is clicked + Use step option reflexOnly in conjunction with step option reflex to automagically hide the "next" button in the tour, and only continue when the user clicks the element: + var tourSteps = [ + { + element: "#myButton", + reflex: true, + reflexOnly: true, + title: "Click it", + content: "Click to continue, or you're stuck" + } + ]; + +---------------- + 5. Call function when element is missing + If the element specified in the step (static or dynamically determined as per feature #3), onElementUnavailable is called. + Function signature: function(tour, stepNumber) {} + Option is available at global and per step levels. + + function tourBroken(tour, stepNumber) + { + alert("Uhoh, tour element is done broke on step number " + stepNumber); + } + + var tourSteps = [ + { + element: "#btnMagic", + onElementUnavailable: tourBroken, + title: "Hold my beer", + content: "now watch this" + } + ]; + +--------------- + 6. Scroll flicker / continue reload fixed + Original Tour constantly reloaded the current tour step on scroll & similar events. This produced flickering, constant reloads and therefore constant calls to all the step function calls. + This is now fixed. Scrolling the browser window does not cause the tour step to reload. + + IMPORTANT: orphan steps are stuck to the center of the screen. However steps linked to elements ALWAYS stay stuck to their element, even if user scrolls the element & tour popover + off the screen. This is my personal preference, as original functionality of tour step moving with the scroll even when the element was off the viewport seemed strange. + +--------------- + 7. Progress bar & progress text: + Use the following options globally or per step to show tour progress: + showProgressBar - shows a bootstrap progress bar for tour progress at the top of the tour content + showProgressText - shows a textual progress (N/X, i.e.: 1/24 for slide 1 of 24) in the tour title + + var tourSteps = [ + { + element: "#inputBanana", + title: "Bananas!", + content: "Bananas are yellow, except when they're not", + }, + { + element: "#inputOranges", + title: "Oranges!", + content: "Oranges are not bananas", + showProgressBar: false, // don't show the progress bar on this step only + showProgressText: false, // don't show the progress text on this step only + } + ]; + var Tour=new Tour({ + framework: "bootstrap3", // or "bootstrap4" depending on your version of bootstrap + steps: tourSteps, + showProgressBar: true, // default show progress bar + showProgressText: true, // default show progress text + }); + + 7b. Customize the progressbar/progress text: + In conjunction with 7a, provide the following functions globally or per step to draw your own progressbar/progress text: + + getProgressBarHTML(percent) + getProgressTextHTML(stepNumber, percent, stepCount) + + These will be called when each step is shown, with the appropriate percentage/step number etc passed to your function. Return an HTML string of a "drawn" progress bar/progress text + which will be directly inserted into the tour step. + + Example: + var tourSteps = [ + { + element: "#inputBanana", + title: "Bananas!", + content: "Bananas are yellow, except when they're not", + }, + { + element: "#inputOranges", + title: "Oranges!", + content: "Oranges are not bananas", + getProgressBarHTML: function(percent) + { + // override the global progress bar function for this step + return '
You're ' + percent + ' of the way through!
'; + } + } + ]; + var Tour=new Tour({ + steps: tourSteps, + showProgressBar: true, // default show progress bar + showProgressText: true, // default show progress text + getProgressBarHTML: function(percent) + { + // default progress bar for all steps. Return valid HTML to draw the progress bar you want + return '
'; + }, + getProgressTextHTML: function(stepNumber, percent, stepCount) + { + // default progress text for all steps + return 'Slide ' + stepNumber + "/" + stepCount; + }, + + }); + +---------------- + 8. Prevent interaction with element + Sometimes you want to highlight a DOM element (button, input field) for a tour step, but don't want the user to be able to interact with it. + Use preventInteraction to stop the user touching the element: + + var tourSteps = [ + { + element: "#btnMCHammer", + preventInteraction: true, + title: "Hammer Time", + content: "You can't touch this" + } + ]; + +---------------- + 9. Wait for an element to appear before continuing tour + Sometimes a tour step element might not be immediately ready because of transition effects etc. This is a specific issue with bootstrap select, which is relatively slow to show the selectpicker + dropdown after clicking. + Use delayOnElement to instruct Tour to wait for **ANY** element to appear before showing the step (or crapping out due to missing element). Yes this means the tour step element can be one DOM + element, but the delay will wait for a completely separate DOM element to appear. This is really useful for hidden divs etc. + Use in conjunction with onElementUnavailable for robust tour step handling. + + delayOnElement is an object with the following: + delayOnElement: { + delayElement: "#waitForMe", // the element to wait to become visible, or the string literal "element" to use the step element + maxDelay: 2000, // optional milliseconds to wait/timeout for the element, before crapping out. If maxDelay is not specified, this is 2000ms by default + } + + var tourSteps = [ + { + element: "#btnPrettyTransition", + delayOnElement: { + delayElement: "element" // use string literal "element" to wait for this step's element, i.e.: #btnPrettyTransition + }, + title: "Ages", + content: "This button takes ages to appear" + }, + { + element: "#inputUnrelated", + delayOnElement: { + delayElement: "#divStuff" // wait until DOM element "divStuff" is visible before showing this tour step against DOM element "inputUnrelated" + }, + title: "Waiting", + content: "This input is nice, but you only see this step when the other div appears" + }, + { + element: "#btnDontForgetThis", + delayOnElement: { + delayElement: "element", // use string literal "element" to wait for this step's element, i.e.: #btnDontForgetThis + maxDelay: 5000 // wait 5 seconds for it to appear before timing out + }, + title: "Cool", + content: "Remember the onElementUnavailable option!", + onElementUnavailable: function(tour, stepNumber) + { + // This will be called if btnDontForgetThis is not visible after 5 seconds + console.log("Well that went badly wrong"); + } + }, + ]; + +---------------- + 10. Trigger when modal closes + If tour element is a modal, or is a DOM element inside a modal, the element can disappear "at random" if the user dismisses the dialog. + In this case, onModalHidden global and per step function is called. Only functional when step is not an orphan. + This is useful if a tour includes a step that launches a modal, and the tour requires the user to take some actions inside the modal before OK'ing it and moving to the next + tour step. + + Return (int) step number to immediately move to that step + Return exactly false to not change tour state in any way - this is useful if you need to reshow the modal because some validation failed + Return anything else to move to the next step + + element === Bootstrap modal, or element parent === bootstrap modal is automatically detected. + + var Tour=new Tour({ + steps: tourSteps, + framework: "bootstrap3", // or "bootstrap4" depending on your version of bootstrap + onModalHidden: function(tour, stepNumber) + { + console.log("Well damn, this step's element was a modal, or inside a modal, and the modal just done got dismissed y'all. Moving to step 3."); + + // move to step number 3 + return 3; + }, + }); + + + var Tour=new Tour({ + steps: tourSteps, + onModalHidden: function(tour, stepNumber) + { + if(validateSomeModalContent() == false) + { + // The validation failed, user dismissed modal without properly taking actions. + // Show the modal again + showModalAgain(); + + // Instruct tour to stay on same step + return false; + } + else + { + // Content was valid. Return null or do nothing to instruct tour to continue to next step + } + }, + }); + + + + 10b. Handle Dialogs and BootstrapDialog plugin better https://nakupanda.github.io/bootstrap3-dialog/ + Plugin makes creating dialogs very easy, but it does some extra stuff to the dialogs and dynamically creates/destroys them. This + causes issues with plugins that want to include a modal dialog in the steps using this plugin. + + To use Tour to highlight an element in a dialog, just use the element ID as you would for any normal step. The dialog will be automatically + detected and handled. + + To use Tour to highlight an entire dialog, set the step element to the dialog div. Tour will automatically realize this is a dialog, and + shift the element to use the modal-content div inside the dialog. This makes life friendly, because you can do this: + + + + Then use element: myModal in the Tour. + + + FOR BOOTSTRAPDIALOG PLUGIN: this plugin creates random UUIDs for the dialog DOM ID. You need to fix the ID to something you know. Do this: + + dlg = new BootstrapDialog.confirm({ + ....all the options... + }); + + // BootstrapDialog gives a random GUID ID for dialog. Give it a proper one + $objModal = dlg.getModal(); + $objModal.attr("id", "myModal"); + dlg.setId("myModal"); + + + Now you can use element: myModal in the tour, even when the dialog is created by BootstrapDialog plugin. + + +---------------- + 11. Fix conflict with Bootstrap Selectpicker: https://github.com/snapappointments/bootstrap-select/ + Selectpicker draws a custom select. Tour now automagically finds and adjusts the selectpicker dropdown so that it appears correctly within the tour + + +---------------- + 12. Call onPreviouslyEnded if tour.start() is called for a tour that has previously ended + See the following github issue: https://github.com/sorich87/bootstrap-tour/issues/720 + Original behavior for a tour that had previously ended was to call onStart() callback, and then abort without calling onEnd(). This has been altered so + that calling start() on a tour that has previously ended (cookie step set to end etc) will now ONLY call onPreviouslyEnded(). + + This restores the functionality that allows app JS to simply call tour.start() on page load, and the Tour will now only call onStart() / onEnd() when + the tour really is started or ended. + + var Tour=new Tour({ + steps: [ ..... ], + framework: "bootstrap3", // or "bootstrap4" depending on your version of bootstrap + onPreviouslyEnded: function(tour) + { + console.log("Looks like this tour has already ended"); + }, + }); + + tour.start(); + +---------------- + 13. Switch between Bootstrap 3 or 4 (popover methods, template) automatically using tour options, or use a custom template + With thanks to this thread: https://github.com/sorich87/bootstrap-tour/pull/643 + + Tour is compatible with bootstrap 3 and 4 if the right template and framework is used for the popover. Bootstrap3 framework compatibility is used by default. + + To select the correct template and framework, use the "framework" global option. Note this option does more than just select a template, it also changes which + methods are used to manage the Tour popovers to be BS3 or BS4 compatible. + + var Tour=new Tour({ + steps: tourSteps, + template: null, // template option is null by default. Tourist will use the appropriate template + // for the framework version, in this case BS3 as per next option + framework: "bootstrap3", // can be string literal "bootstrap3" or "bootstrap4" + }); + + + To use a custom template, use the "template" global option: + + var Tour=new Tour({ + steps: tourSteps, + framework: "bootstrap4", // can be string literal "bootstrap3" or "bootstrap4" + template: '' + }); + + Review the following logic: + - If template == null, default framework template is used based on whether framework is set to "bootstrap3" or "bootstrap4" + - If template != null, the specified template is always used + - If framework option is not literal "bootstrap3" or "bootstrap4", error will occur + + + To add additional templates, search the code for "PLACEHOLDER: TEMPLATES LOCATION". This will take you to an array that contains the templates, simply edit + or add as required. + + +---------------- + 14. Options to manipulate the Bootstrap sanitizer, and fix the sanitizer related breaking change in BS 3.4.x + BS 3.4.1 added a sanitizer to popover and tooltips - this breaking change strips non-whitelisted DOM elements from popover content, title etc. + See: https://getbootstrap.com/docs/3.4/javascript/#js-sanitizer and https://blog.getbootstrap.com/2019/02/13/bootstrap-4-3-1-and-3-4-1/ + + This Bootstrap change resulted in Tour navigation buttons being killed from the DOM: https://github.com/sorich87/bootstrap-tour/issues/723#issuecomment-471107788 + + This has been fixed in code, Tour navigation buttons now appear and work by default. + + To prevent future similar reoccurrences, and also allow the manipulation of the sanitizer "allowed list" for Tours that want to add extra content into + tour steps, two features added to global options. To understand the purpose and operation of these features, review the following information on the Bootstrap + sanitizer: https://getbootstrap.com/docs/3.4/javascript/#js-sanitizer + + --IMPORTANT NOTE-- SECURITY RISK: if you do not understand the purpose of the sanitizer, why it exists in bootstrap or how it relates to Tour, do not use these options. + + Global options: + + sanitizeWhitelist: specify an object that will be merged with the Bootstrap Popover default whitelist. Use the same structure as the default Bootstrap + whitelist. + + sanitizeFunction: specify a function that will be used to sanitize Tour content, with the following signature: string function(content). + Specifying a function for this option will cause sanitizeWhitelist to be ignored. + Specifying anything other than a function for this option will be ignored, and sanitizeWhitelist will be used + + Examples: + + Allow tour step content to include a button with attributes data-someplugin1="..." and data-somethingelse="...". Allow content to include a selectpicker. + var Tour=new Tour({ + steps: tourSteps, + sanitizeWhitelist: { + "button" : ["data-someplugin1", "data-somethingelse"], // allows