We read every piece of feedback, and take your input very seriously.
To see all available qualifiers, see our documentation.
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
New language relevant PR in upstream repo: joomla/joomla-cms#43814 Here are the upstream changes:
diff --git a/administrator/components/com_actionlogs/config.xml b/administrator/components/com_actionlogs/config.xml index 93af8df4ded39..59c301dbecd62 100644 --- a/administrator/components/com_actionlogs/config.xml +++ b/administrator/components/com_actionlogs/config.xml @@ -30,7 +30,7 @@ label="COM_ACTIONLOGS_LOG_EXTENSIONS_LABEL" multiple="true" layout="joomla.form.field.list-fancy-select" - default="com_banners,com_cache,com_categories,com_checkin,com_config,com_contact,com_content,com_fields,com_installer,com_media,com_menus,com_messages,com_modules,com_newsfeeds,com_plugins,com_redirect,com_scheduler,com_tags,com_templates,com_users" + default="com_banners,com_cache,com_categories,com_checkin,com_config,com_contact,com_content,com_fields,com_guidedtours,com_installer,com_media,com_menus,com_messages,com_modules,com_newsfeeds,com_plugins,com_redirect,com_scheduler,com_tags,com_templates,com_users" /> <field name="loggable_api" diff --git a/administrator/components/com_admin/sql/updates/mysql/5.2.0-2024-07-19.sql b/administrator/components/com_admin/sql/updates/mysql/5.2.0-2024-07-19.sql new file mode 100644 index 0000000000000..284938a777e2d --- /dev/null +++ b/administrator/components/com_admin/sql/updates/mysql/5.2.0-2024-07-19.sql @@ -0,0 +1,7 @@ +-- +-- Add the Guided Tours selectable option to the User Action Logs +-- +INSERT INTO `#__action_logs_extensions` (`extension`) VALUES ('com_guidedtours'); + +INSERT INTO `#__action_log_config` (`type_title`, `type_alias`, `id_holder`, `title_holder`, `table_name`, `text_prefix`) VALUES +('guidedtour', 'com_guidedtours.state', 'id', 'title', '#__guidedtours', 'PLG_ACTIONLOG_JOOMLA'); diff --git a/administrator/components/com_admin/sql/updates/postgresql/5.2.0-2024-07-19.sql b/administrator/components/com_admin/sql/updates/postgresql/5.2.0-2024-07-19.sql new file mode 100644 index 0000000000000..479c6b1fc2729 --- /dev/null +++ b/administrator/components/com_admin/sql/updates/postgresql/5.2.0-2024-07-19.sql @@ -0,0 +1,7 @@ +-- +-- Add the Guided Tours selectable option to the User Action Logs +-- +INSERT INTO "#__action_logs_extensions" ("extension") VALUES ("com_guidedtours"); + +INSERT INTO "#__action_log_config" ("type_title", "type_alias", "id_holder", "title_holder", "table_name", "text_prefix") VALUES +('guidedtour', 'com_guidedtours.state', 'id', 'title', '#__guidedtours', 'PLG_ACTIONLOG_JOOMLA'); diff --git a/administrator/components/com_guidedtours/config.xml b/administrator/components/com_guidedtours/config.xml index b387dae127f66..2ff0681eaa56b 100644 --- a/administrator/components/com_guidedtours/config.xml +++ b/administrator/components/com_guidedtours/config.xml @@ -1,5 +1,28 @@ <?xml version="1.0" encoding="UTF-8"?> <config> + <fieldset name="guidedtours_config" label="COM_GUIDEDTOURS"> + <field + name="allowTourAutoStart" + type="radio" + label="COM_GUIDEDTOURS_CONFIG_USER_ALLOWTOURAUTOSTART_LABEL" + description="COM_GUIDEDTOURS_CONFIG_USER_ALLOWTOURAUTOSTART_DESCRIPTION" + layout="joomla.form.field.radio.switcher" + default="1" + > + <option value="0">JNO</option> + <option value="1">JYES</option> + </field> + + <field + name="delayed_time" + type="text" + label="COM_GUIDEDTOURS_CONFIG_DELAYED_TIME_LABEL" + description="COM_GUIDEDTOURS_CONFIG_DELAYED_TIME_DESCRIPTION" + default="60" + size="small" + showon="allowTourAutoStart:1" + /> + </fieldset> <fieldset name="permissions" label="JCONFIG_PERMISSIONS_LABEL" diff --git a/administrator/components/com_guidedtours/src/Controller/AjaxController.php b/administrator/components/com_guidedtours/src/Controller/AjaxController.php new file mode 100644 index 0000000000000..031a4af78e3ed --- /dev/null +++ b/administrator/components/com_guidedtours/src/Controller/AjaxController.php @@ -0,0 +1,124 @@ +<?php + +/** + * @package Joomla.Administrator + * @subpackage com_guidedtours + * + * @copyright (C) 2024 Open Source Matters, Inc. <https://www.joomla.org> + * @license GNU General Public License version 2 or later; see LICENSE.txt + */ + +namespace Joomla\Component\Guidedtours\Administrator\Controller; + +use Joomla\CMS\Event\AbstractEvent; +use Joomla\CMS\Language\Text; +use Joomla\CMS\MVC\Controller\BaseController; +use Joomla\CMS\Plugin\PluginHelper; +use Joomla\CMS\Response\JsonResponse; + +// phpcs:disable PSR1.Files.SideEffects +\defined('_JEXEC') or die; +// phpcs:enable PSR1.Files.SideEffects + +/** + * The guided tours controller for ajax requests. + * + * @since __DEPLOY_VERSION__ + */ +class AjaxController extends BaseController +{ + /** + * Ajax call used when cancelling, skipping or completing a tour. + * It allows: + * - the trigering of before and after events the user state is recorded + * - the recording of the user behavior in the action logs + */ + public function fetchUserState() + { + $user = $this->app->getIdentity(); + + $tourId = $this->app->input->getInt('tid', 0); + $stepNumber = $this->app->input->getString('sid', ''); + $context = $this->app->input->getString('context', ''); + + if ($user != null && $user->id > 0) { + $actionState = ''; + + switch ($context) { + case 'tour.complete': + $actionState = 'completed'; + break; + case 'tour.cancel': + $actionState = 'delayed'; + break; + case 'tour.skip': + $actionState = 'skipped'; + break; + } + + PluginHelper::importPlugin('guidedtours'); + + // event onBeforeTourSaveUserState before save user tour state + $beforeEvent = AbstractEvent::create( + 'onBeforeTourSaveUserState', + [ + 'subject' => new \stdClass(), + 'tourId' => $tourId, + 'actionState' => $actionState, + 'stepNumber' => $stepNumber, + ] + ); + + $this->app->getDispatcher()->dispatch('onBeforeTourSaveUserState', $beforeEvent); + + // Save the tour state only when the tour auto-starts. + $tourModel = $this->getModel('Tour', 'Administrator'); + if ($tourModel->isAutostart($tourId)) { + $result = $tourModel->saveTourUserState($tourId, $actionState); + if ($result) { + $message = Text::sprintf('COM_GUIDEDTOURS_USERSTATE_STATESAVED', $user->id, $tourId); + } else { + $message = Text::sprintf('COM_GUIDEDTOURS_USERSTATE_STATENOTSAVED', $user->id, $tourId); + } + } else { + $result = false; + $message = Text::sprintf('COM_GUIDEDTOURS_USERSTATE_STATENOTSAVED', $user->id, $tourId); + } + + // event onAfterTourSaveUserState after save user tour state (may override message) + $afterEvent = AbstractEvent::create( + 'onAfterTourSaveUserState', + [ + 'subject' => new \stdClass(), + 'tourId' => $tourId, + 'actionState' => $actionState, + 'stepNumber' => $stepNumber, + 'result' => $result, + 'message' => &$message, + ] + ); + + $this->app->getDispatcher()->dispatch('onAfterTourSaveUserState', $afterEvent); + + // Construct the response data + $data = [ + 'tourId' => $tourId, + 'stepId' => $stepNumber, + 'context' => $context, + 'state' => $actionState, + ]; + echo new JsonResponse($data, $message); + $this->app->close(); + } else { + // Construct the response data + $data = [ + 'success' => false, + 'tourId' => $tourId, + ]; + + $message = Text::_('COM_GUIDEDTOURS_USERSTATE_CONNECTEDONLY'); + echo new JsonResponse($data, $message, true); + $this->app->close(); + } + } +} diff --git a/administrator/components/com_guidedtours/src/Model/TourModel.php b/administrator/components/com_guidedtours/src/Model/TourModel.php index edee94fc6bbbe..8cc0d50181c2d 100644 --- a/administrator/components/com_guidedtours/src/Model/TourModel.php +++ b/administrator/components/com_guidedtours/src/Model/TourModel.php @@ -10,6 +10,7 @@ namespace Joomla\Component\Guidedtours\Administrator\Model; +use Joomla\CMS\Date\Date; use Joomla\CMS\Factory; use Joomla\CMS\Language\Text; use Joomla\CMS\Log\Log; @@ -530,20 +531,28 @@ public function setAutostart($id, $autostart) /** * Retrieve a tour's autostart value * - * @param string $uid the uid of a tour + * @param string $pk the id or uid of a tour + * + * @return boolean * * @since 5.1.0 */ - public function isAutostart($uid) + public function isAutostart($pk): bool { $db = $this->getDatabase(); $query = $db->getQuery(true) ->select($db->quoteName('autostart')) ->from($db->quoteName('#__guidedtours')) - ->where($db->quoteName('published') . ' = 1') - ->where($db->quoteName('uid') . ' = :uid') - ->bind(':uid', $uid, ParameterType::STRING); + ->where($db->quoteName('published') . ' = 1'); + + if (\is_integer($pk)) { + $query->where($db->quoteName('id') . ' = :id') + ->bind(':id', $pk, ParameterType::INTEGER); + } else { + $query->where($db->quoteName('uid') . ' = :uid') + ->bind(':uid', $pk, ParameterType::STRING); + } $db->setQuery($query); @@ -558,4 +567,64 @@ public function isAutostart($uid) return $result; } + + /** + * Save a tour state for a specific user. + * + * @param int $id The id of the tour + * @param string $state The label of the state to be saved (completed, delayed or skipped) + * + * @return boolean + * + * @since __DEPLOY_VERSION__ + */ + public function saveTourUserState($id, $state = ''): bool + { + $user = $this->getCurrentUser(); + $db = $this->getDatabase(); + + $profileKey = 'guidedtour.id.' . $id; + + // Check if the profile key already exists. + $query = $db->getQuery(true) + ->select($db->quoteName('profile_value')) + ->from($db->quoteName('#__user_profiles')) + ->where($db->quoteName('user_id') . ' = :user_id') + ->where($db->quoteName('profile_key') . ' = :profileKey') + ->bind(':user_id', $user->id, ParameterType::INTEGER) + ->bind(':profileKey', $profileKey, ParameterType::STRING); + + try { + $result = $db->setQuery($query)->loadResult(); + } catch (\Exception $e) { + return false; + } + + $tourState = []; + + $tourState['state'] = $state; + if ($state === 'delayed') { + $tourState['time'] = Date::getInstance(); + } + + $profileObject = (object)[ + 'user_id' => $user->id, + 'profile_key' => $profileKey, + 'profile_value' => json_encode($tourState), + 'ordering' => 0, + ]; + + if (!\is_null($result)) { + $values = json_decode($result, true); + + // The profile is updated only when delayed. 'Completed' and 'Skipped' are final + if (!empty($values) && $values['state'] === 'delayed') { + $db->updateObject('#__user_profiles', $profileObject, ['user_id', 'profile_key']); + } + } else { + $db->insertObject('#__user_profiles', $profileObject); + } + + return true; + } } diff --git a/administrator/components/com_users/forms/user.xml b/administrator/components/com_users/forms/user.xml index 1946e3f3ca643..199662a2c8207 100644 --- a/administrator/components/com_users/forms/user.xml +++ b/administrator/components/com_users/forms/user.xml @@ -164,6 +164,18 @@ <option value="dark">COM_USERS_USER_COLORSCHEME_OPTION_DARK</option> </field> + <field + name="allowTourAutoStart" + type="list" + label="COM_USERS_USER_ALLOWTOURAUTOSTART_LABEL" + default="" + validate="options" + > + <option value="">JOPTION_USE_DEFAULT</option> + <option value="0">JNO</option> + <option value="1">JYES</option> + </field> + <field name="admin_language" type="language" diff --git a/administrator/language/en-GB/com_guidedtours.ini b/administrator/language/en-GB/com_guidedtours.ini index e45fbb95000c9..627baef477610 100644 --- a/administrator/language/en-GB/com_guidedtours.ini +++ b/administrator/language/en-GB/com_guidedtours.ini @@ -6,6 +6,10 @@ COM_GUIDEDTOURS="Guided Tours" COM_GUIDEDTOURS_AUTOSTART_DESC="Start the tour automatically when a user reaches the context in which the tour should be displayed." COM_GUIDEDTOURS_AUTOSTART_LABEL="Auto Start" +COM_GUIDEDTOURS_CONFIG_DELAYED_TIME_DESCRIPTION="The amount of time (in minutes) a tour is delayed after being cancelled by the user and until it is shown again (only when a tour is set to start automatically).<br>For instance, enter 60 for 1 hour, 1440 for 24 hours, 10080 for 1 week." +COM_GUIDEDTOURS_CONFIG_DELAYED_TIME_LABEL="Auto Start Time Delay (in minutes)" +COM_GUIDEDTOURS_CONFIG_USER_ALLOWTOURAUTOSTART_DESCRIPTION="Turn on or off the auto starting functionality of tours." +COM_GUIDEDTOURS_CONFIG_USER_ALLOWTOURAUTOSTART_LABEL="Allow Auto Starting Tours" COM_GUIDEDTOURS_CONFIGURATION="Guided Tours: Options" COM_GUIDEDTOURS_DESCRIPTION="Description" COM_GUIDEDTOURS_DESCRIPTION_TRANSLATION="Description (%s)" @@ -90,4 +94,7 @@ COM_GUIDEDTOURS_TYPE_REDIRECT_URL_DESC="Enter the relative URL of the page you w COM_GUIDEDTOURS_TYPE_REDIRECT_URL_LABEL="Relative URL" COM_GUIDEDTOURS_URL_LABEL="Relative URL" COM_GUIDEDTOURS_URL_DESC="Enter the relative URL of the page from where you want to Start the tour, e.g administrator/index.php?option=com_guidedtours&view=tours for the tours' list page." +COM_GUIDEDTOURS_USERSTATE_CONNECTEDONLY="Tour User state action is only for connected users." +COM_GUIDEDTOURS_USERSTATE_STATENOTSAVED="Tour User state not saved for user %1$s tour %2$s." +COM_GUIDEDTOURS_USERSTATE_STATESAVED="Tour User state saved for user %1$s tour %2$s." COM_GUIDEDTOURS_XML_DESCRIPTION="Component for managing Guided Tours functionality." diff --git a/administrator/language/en-GB/com_users.ini b/administrator/language/en-GB/com_users.ini index be813a51a60ca..4120dbfd6deaf 100644 --- a/administrator/language/en-GB/com_users.ini +++ b/administrator/language/en-GB/com_users.ini @@ -394,6 +394,7 @@ COM_USERS_USERS_N_ITEMS_DELETED="%d users deleted." COM_USERS_USERS_N_ITEMS_DELETED_1="User deleted." COM_USERS_USERS_TABLE_CAPTION="Users" COM_USERS_USER_ACCOUNT_DETAILS="Account Details" +COM_USERS_USER_ALLOWTOURAUTOSTART_LABEL="Allow Auto Starting Tours" COM_USERS_USER_BACKUPCODE="Backup Code" COM_USERS_USER_BACKUPCODES="Backup Codes" COM_USERS_USER_BACKUPCODES_CAPTIVE_PROMPT="If you do not have access to your usual Multi-factor Authentication method use any of your Backup Codes in the field below. Please remember that this emergency backup code cannot be reused." diff --git a/administrator/language/en-GB/plg_actionlog_joomla.ini b/administrator/language/en-GB/plg_actionlog_joomla.ini index 408931eb795d5..69d026dbc27e8 100644 --- a/administrator/language/en-GB/plg_actionlog_joomla.ini +++ b/administrator/language/en-GB/plg_actionlog_joomla.ini @@ -63,3 +63,7 @@ PLG_ACTIONLOG_JOOMLA_EXTENSION_INSTALLED="User <a href='{accountlink}'>{username PLG_ACTIONLOG_JOOMLA_EXTENSION_UNINSTALLED="User <a href='{accountlink}'>{username}</a> uninstalled the {type} {extension_name}" PLG_ACTIONLOG_JOOMLA_EXTENSION_UPDATED="User <a href='{accountlink}'>{username}</a> updated the {type} {extension_name}" PLG_ACTIONLOG_JOOMLA_PLUGIN_INSTALLED="User <a href='{accountlink}'>{username}</a> installed the plugin <a href='index.php?option=com_plugins&task=plugin.edit&extension_id={id}'>{extension_name}</a>" +; Guided Tours +PLG_ACTIONLOG_JOOMLA_GUIDEDTOURS_TOURCOMPLETED="User <a href='{accountlink}'>{username}</a> completed the tour '{title}'" +PLG_ACTIONLOG_JOOMLA_GUIDEDTOURS_TOURDELAYED="User <a href='{accountlink}'>{username}</a> delayed the tour '{title}'" +PLG_ACTIONLOG_JOOMLA_GUIDEDTOURS_TOURSKIPPED="User <a href='{accountlink}'>{username}</a> skipped the tour '{title}'" diff --git a/administrator/language/en-GB/plg_system_guidedtours.ini b/administrator/language/en-GB/plg_system_guidedtours.ini index b812525479907..97ca225da8871 100644 --- a/administrator/language/en-GB/plg_system_guidedtours.ini +++ b/administrator/language/en-GB/plg_system_guidedtours.ini @@ -8,7 +8,10 @@ PLG_SYSTEM_GUIDEDTOURS_BACK="Back" PLG_SYSTEM_GUIDEDTOURS_COMPLETE="Complete" PLG_SYSTEM_GUIDEDTOURS_COULD_NOT_LOAD_THE_TOUR="Could not start the tour. No valid ID given." PLG_SYSTEM_GUIDEDTOURS_DESCRIPTION="System Guided Tour Plugin" +PLG_SYSTEM_GUIDEDTOURS_HIDE_FOREVER="Hide Forever" PLG_SYSTEM_GUIDEDTOURS_NEXT="Next" PLG_SYSTEM_GUIDEDTOURS_START="Start" PLG_SYSTEM_GUIDEDTOURS_STEP_NUMBER_OF="Step {number} of {total}" ; Do not translate the text between the {} PLG_SYSTEM_GUIDEDTOURS_TOUR_ERROR="Oh no! It looks like your tour is off track. The tour exited and cannot run." +PLG_SYSTEM_GUIDEDTOURS_TOUR_ERROR_RESPONSE="Something went wrong while saving tour state information! Response error." +PLG_SYSTEM_GUIDEDTOURS_TOUR_INVALID_RESPONSE="Something went wrong while saving tour state information! Response is invalid." diff --git a/build/media_source/plg_system_guidedtours/js/guidedtours.es6.js b/build/media_source/plg_system_guidedtours/js/guidedtours.es6.js index 9cd2e75e3ed5d..9756d2344d03c 100644 --- a/build/media_source/plg_system_guidedtours/js/guidedtours.es6.js +++ b/build/media_source/plg_system_guidedtours/js/guidedtours.es6.js @@ -15,6 +15,71 @@ function emptyStorage() { sessionStorage.removeItem('tourId'); sessionStorage.removeItem('tourToken'); sessionStorage.removeItem('previousStepUrl'); + sessionStorage.removeItem('skipTour'); + sessionStorage.removeItem('autoTourId'); +} + +/** + Synchronize tour state for this user in their account/profile + tid = tour ID + sid = step number (the step the user is on) + state = state of the tour (completed, skipped, cancelled) +*/ +function fetchTourState(tid, sid, context) { + const fetchUrl = 'index.php?option=com_guidedtours&task=ajax.fetchUserState&format=json'; + Joomla.request({ + url: `${fetchUrl}&tid=${tid}&sid=${sid}&context=${context}`, + method: 'GET', + perform: true, + onSuccess: (response) => { + try { + JSON.parse(response); + } catch (e) { + Joomla.renderMessages({ error: [Joomla.Text._('PLG_SYSTEM_GUIDEDTOURS_TOUR_INVALID_RESPONSE')] }, 'gt'); + return false; + } + return true; + }, + onError: () => { + Joomla.renderMessages({ error: [Joomla.Text._('PLG_SYSTEM_GUIDEDTOURS_TOUR_ERROR_RESPONSE')] }); + return false; + }, + }); +} + +/** + Stop tour on some specific context + - tour.complete + - tour.cancel + - tour.skip Only autostart tours, to never display again +*/ +function stopTour(tour, context) { + const tid = sessionStorage.getItem('tourId'); + let sid = sessionStorage.getItem('currentStepId'); + + if (sid === 'tourinfo') { + sid = 1; + } else { + sid = Number(sid) + 1; + } + + let trueContext = context; + if (context === 'tour.cancel' && sessionStorage.getItem('skipTour') === 'true') { + trueContext = 'tour.skip'; + } + + if (trueContext === 'tour.cancel' || trueContext === 'tour.skip' || trueContext === 'tour.complete') { + // ajax call to set the user state + fetchTourState(tid, sid, trueContext); + + // close the tour + emptyStorage(); + tour.steps = []; + + return true; // cf. https://docs.shepherdpro.com/api/tour/classes/tour/#cancel + } + + return false; // wrong context } function getTourInstance() { @@ -35,9 +100,10 @@ function getTourInstance() { }); tour.on('cancel', () => { - emptyStorage(); - - tour.steps = []; + // Test that a tour exists still, it may have already been emptied when skipping the tour + if (sessionStorage.getItem('tourId')) { + stopTour(tour, 'tour.cancel'); + } }); return tour; @@ -70,6 +136,7 @@ function enableButton(eventElement) { element.removeAttribute('disabled'); element.classList.remove('disabled'); } + function disableButton(eventElement) { const element = eventElement instanceof Event ? document.querySelector(`.step-next-button-${eventElement.currentTarget.step_id}`) : eventElement; element.setAttribute('disabled', 'disabled'); @@ -297,36 +364,57 @@ function addStepToTourButton(tour, stepObj, buttons) { tour.addStep(step); } +function addStartButton(tour, buttons, label) { + buttons.push({ + text: label, + classes: 'btn btn-primary shepherd-button-primary', + action() { + return this.next(); + }, + }); +} + +function addSkipButton(tour, buttons) { + buttons.push({ + text: Joomla.Text._('PLG_SYSTEM_GUIDEDTOURS_HIDE_FOREVER'), + classes: 'btn btn-secondary shepherd-button-secondary', + action() { + sessionStorage.setItem('skipTour', 'true'); + return this.cancel(); + }, + }); +} + function showTourInfo(tour, stepObj) { + const buttons = []; + if (sessionStorage.getItem('autoTourId') === sessionStorage.getItem('tourId')) { + addSkipButton(tour, buttons); + } + addStartButton(tour, buttons, stepObj.start_label); + tour.addStep({ title: stepObj.title, text: stepObj.description, classes: 'shepherd-theme-arrows', - buttons: [ - { - classes: 'btn btn-primary shepherd-button-primary', - action() { - return this.next(); - }, - text: Joomla.Text._('PLG_SYSTEM_GUIDEDTOURS_START'), - }, - ], + buttons, id: 'tourinfo', when: { show() { sessionStorage.setItem('currentStepId', 'tourinfo'); + sessionStorage.setItem('skipTour', 'false'); addProgressIndicator(this.getElement(), 1, sessionStorage.getItem('stepCount')); }, }, }); } -function pushCompleteButton(buttons) { +function pushCompleteButton(tour, buttons) { buttons.push({ text: Joomla.Text._('PLG_SYSTEM_GUIDEDTOURS_COMPLETE'), classes: 'btn btn-primary shepherd-button-primary', action() { - return this.cancel(); + stopTour(tour, 'tour.complete'); + return this.complete(); }, }); } @@ -529,7 +617,7 @@ function startTour(obj) { pushNextButton(buttons, obj.steps[index]); } } else { - pushCompleteButton(buttons); + pushCompleteButton(obj, buttons); } addStepToTourButton(tour, obj.steps[index], buttons); @@ -576,23 +664,23 @@ function loadTour(tourId) { } // Opt-in Start buttons -document.querySelector('body').addEventListener('click', ({ target }) => { +document.querySelector('body').addEventListener('click', (event) => { // Click somewhere else - if (!target || !target.classList.contains('button-start-guidedtour')) { + if (!event.target || !event.target.classList.contains('button-start-guidedtour')) { return; } // Click button but missing data-id if ( - (!target.hasAttribute('data-id') || target.getAttribute('data-id') <= 0) - && (!target.hasAttribute('data-gt-uid') || target.getAttribute('data-gt-uid') === '') + (!event.target.hasAttribute('data-id') || event.target.getAttribute('data-id') <= 0) + && (!event.target.hasAttribute('data-gt-uid') || event.target.getAttribute('data-gt-uid') === '') ) { Joomla.renderMessages({ error: [Joomla.Text._('PLG_SYSTEM_GUIDEDTOURS_COULD_NOT_LOAD_THE_TOUR')] }); return; } sessionStorage.setItem('tourToken', String(Joomla.getOptions('com_guidedtours.token'))); - loadTour(target.getAttribute('data-id') || target.getAttribute('data-gt-uid')); + loadTour(event.target.getAttribute('data-id') || event.target.getAttribute('data-gt-uid')); }); // Start a given tour @@ -601,6 +689,7 @@ let tourId = sessionStorage.getItem('tourId'); // Autostart tours have priority if (Joomla.getOptions('com_guidedtours.autotour', '') !== '') { sessionStorage.setItem('tourToken', String(Joomla.getOptions('com_guidedtours.token'))); + sessionStorage.setItem('autoTourId', String(Joomla.getOptions('com_guidedtours.autotour'))); tourId = Joomla.getOptions('com_guidedtours.autotour'); } diff --git a/components/com_users/forms/frontend.xml b/components/com_users/forms/frontend.xml index 95a4ec7d3aff1..61bc7ac9969b6 100644 --- a/components/com_users/forms/frontend.xml +++ b/components/com_users/forms/frontend.xml @@ -44,6 +44,18 @@ <option value="light">COM_USERS_USER_COLORSCHEME_OPTION_LIGHT</option> <option value="dark">COM_USERS_USER_COLORSCHEME_OPTION_DARK</option> </field> + + <field + name="allowTourAutoStart" + type="list" + label="COM_USERS_USER_ALLOWTOURAUTOSTART_LABEL" + default="" + validate="options" + > + <option value="">JOPTION_USE_DEFAULT</option> + <option value="0">JNO</option> + <option value="1">JYES</option> + </field> </fieldset> </fields> </form> diff --git a/installation/sql/mysql/base.sql b/installation/sql/mysql/base.sql index 15e323420cae8..6301dfdc950c9 100644 --- a/installation/sql/mysql/base.sql +++ b/installation/sql/mysql/base.sql @@ -188,7 +188,7 @@ INSERT INTO `#__extensions` (`package_id`, `name`, `type`, `element`, `folder`, (0, 'com_fields', 'component', 'com_fields', '', 1, 1, 1, 0, 1, '', '', ''), (0, 'com_associations', 'component', 'com_associations', '', 1, 1, 1, 0, 1, '', '', ''), (0, 'com_privacy', 'component', 'com_privacy', '', 1, 1, 1, 0, 1, '', '', ''), -(0, 'com_actionlogs', 'component', 'com_actionlogs', '', 1, 1, 1, 0, 1, '', '{"ip_logging":0,"csv_delimiter":",","loggable_extensions":["com_banners","com_cache","com_categories","com_checkin","com_config","com_contact","com_content","com_fields","com_installer","com_media","com_menus","com_messages","com_modules","com_newsfeeds","com_plugins","com_redirect","com_scheduler","com_tags","com_templates","com_users"]}', ''), +(0, 'com_actionlogs', 'component', 'com_actionlogs', '', 1, 1, 1, 0, 1, '', '{"ip_logging":0,"csv_delimiter":",","loggable_extensions":["com_banners","com_cache","com_categories","com_checkin","com_config","com_contact","com_content","com_fields","com_guidedtours","com_installer","com_media","com_menus","com_messages","com_modules","com_newsfeeds","com_plugins","com_redirect","com_scheduler","com_tags","com_templates","com_users"]}', ''), (0, 'com_workflow', 'component', 'com_workflow', '', 1, 1, 0, 1, 1, '', '{}', ''), (0, 'com_mails', 'component', 'com_mails', '', 1, 1, 1, 1, 1, '', '', ''), (0, 'com_scheduler', 'component', 'com_scheduler', '', 1, 1, 1, 0, 1, '', '{}', ''), diff --git a/installation/sql/mysql/extensions.sql b/installation/sql/mysql/extensions.sql index b1a8b198cedbf..015ff1a1d79c2 100644 --- a/installation/sql/mysql/extensions.sql +++ b/installation/sql/mysql/extensions.sql @@ -831,7 +831,8 @@ INSERT INTO `#__action_logs_extensions` (`id`, `extension`) VALUES (17, 'com_users'), (18, 'com_checkin'), (19, 'com_scheduler'), -(20, 'com_fields'); +(20, 'com_fields'), +(21, 'com_guidedtours'); -- -------------------------------------------------------- @@ -871,7 +872,8 @@ INSERT INTO `#__action_log_config` (`id`, `type_title`, `type_alias`, `id_holder (18, 'banner_client', 'com_banners.client', 'id', 'name', '#__banner_clients', 'PLG_ACTIONLOG_JOOMLA'), (19, 'application_config', 'com_config.application', '', 'name', '', 'PLG_ACTIONLOG_JOOMLA'), (20, 'task', 'com_scheduler.task', 'id', 'title', '#__scheduler_tasks', 'PLG_ACTIONLOG_JOOMLA'), -(21, 'field', 'com_fields.field', 'id', 'title', '#__fields', 'PLG_ACTIONLOG_JOOMLA'); +(21, 'field', 'com_fields.field', 'id', 'title', '#__fields', 'PLG_ACTIONLOG_JOOMLA'), +(22, 'guidedtour', 'com_guidedtours.state', 'id', 'title', '#__guidedtours', 'PLG_ACTIONLOG_JOOMLA'); -- -------------------------------------------------------- diff --git a/installation/sql/postgresql/base.sql b/installation/sql/postgresql/base.sql index f0f97b6150acb..9ade1f50e49a8 100644 --- a/installation/sql/postgresql/base.sql +++ b/installation/sql/postgresql/base.sql @@ -194,7 +194,7 @@ INSERT INTO "#__extensions" ("package_id", "name", "type", "element", "folder", (0, 'com_fields', 'component', 'com_fields', '', 1, 1, 1, 0, 1, '', '', '', 0, 0), (0, 'com_associations', 'component', 'com_associations', '', 1, 1, 1, 0, 1, '', '', '', 0, 0), (0, 'com_privacy', 'component', 'com_privacy', '', 1, 1, 1, 0, 1, '', '', '', 0, 0), -(0, 'com_actionlogs', 'component', 'com_actionlogs', '', 1, 1, 1, 0, 1, '', '{"ip_logging":0,"csv_delimiter":",","loggable_extensions":["com_banners","com_cache","com_categories","com_checkin","com_config","com_contact","com_content","com_fields","com_installer","com_media","com_menus","com_messages","com_modules","com_newsfeeds","com_plugins","com_redirect","com_scheduler","com_tags","com_templates","com_users"]}', '', 0, 0), +(0, 'com_actionlogs', 'component', 'com_actionlogs', '', 1, 1, 1, 0, 1, '', '{"ip_logging":0,"csv_delimiter":",","loggable_extensions":["com_banners","com_cache","com_categories","com_checkin","com_config","com_contact","com_content","com_fields","com_guidedtours","com_installer","com_media","com_menus","com_messages","com_modules","com_newsfeeds","com_plugins","com_redirect","com_scheduler","com_tags","com_templates","com_users"]}', '', 0, 0), (0, 'com_workflow', 'component', 'com_workflow', '', 1, 1, 0, 1, 1, '', '{}', '', 0, 0), (0, 'com_mails', 'component', 'com_mails', '', 1, 1, 1, 1, 1, '', '', '', 0, 0), (0, 'com_scheduler', 'component', 'com_scheduler', '', 1, 1, 1, 0, 1, '', '{}', '', 0, 0), diff --git a/installation/sql/postgresql/extensions.sql b/installation/sql/postgresql/extensions.sql index 5b0bda3f1bc49..af15b88b5cd0d 100644 --- a/installation/sql/postgresql/extensions.sql +++ b/installation/sql/postgresql/extensions.sql @@ -789,9 +789,10 @@ INSERT INTO "#__action_logs_extensions" ("id", "extension") VALUES (17, 'com_users'), (18, 'com_checkin'), (19, 'com_scheduler'), -(20, 'com_fields'); +(20, 'com_fields'), +(21, 'com_guidedtours'); -SELECT setval('#__action_logs_extensions_id_seq', 21, false); +SELECT setval('#__action_logs_extensions_id_seq', 22, false); -- -------------------------------------------------------- -- @@ -832,10 +833,11 @@ INSERT INTO "#__action_log_config" ("id", "type_title", "type_alias", "id_holder (18, 'banner_client', 'com_banners.client', 'id', 'name', '#__banner_clients', 'PLG_ACTIONLOG_JOOMLA'), (19, 'application_config', 'com_config.application', '', 'name', '', 'PLG_ACTIONLOG_JOOMLA'), (20, 'task', 'com_scheduler.task', 'id', 'title', '#__scheduler_tasks', 'PLG_ACTIONLOG_JOOMLA'), -(21, 'field', 'com_fields.field', 'id', 'title', '#__fields', 'PLG_ACTIONLOG_JOOMLA'); +(21, 'field', 'com_fields.field', 'id', 'title', '#__fields', 'PLG_ACTIONLOG_JOOMLA'), +(22, 'guidedtour', 'com_guidedtours.state', 'id', 'title', '#__guidedtours', 'PLG_ACTIONLOG_JOOMLA'); -SELECT setval('#__action_log_config_id_seq', 22, false); +SELECT setval('#__action_log_config_id_seq', 23, false); -- -- Table structure for table `#__action_logs_users` diff --git a/language/en-GB/com_users.ini b/language/en-GB/com_users.ini index b14aaf81e4f45..050ef805a6777 100644 --- a/language/en-GB/com_users.ini +++ b/language/en-GB/com_users.ini @@ -140,6 +140,7 @@ COM_USERS_RESET_REQUEST_ERROR="Error requesting password reset." COM_USERS_RESET_REQUEST_FAILED="Reset password failed: %s" COM_USERS_RESET_REQUEST_LABEL="Please enter the email address for your account. A verification code will be sent to you. Once you have received the verification code, you will be able to choose a new password for your account." COM_USERS_SETTINGS_FIELDSET_LABEL="Basic Settings" +COM_USERS_USER_ALLOWTOURAUTOSTART_LABEL="Allow Auto Starting Tours" COM_USERS_USER_BACKUPCODE="Backup Code" COM_USERS_USER_BACKUPCODES="Backup Codes" COM_USERS_USER_BACKUPCODES_CAPTIVE_PROMPT="If you do not have access to your usual Multi-factor Authentication method use any of your Backup Codes in the field below. Please remember that this emergency backup code cannot be reused." diff --git a/plugins/actionlog/joomla/src/Extension/Joomla.php b/plugins/actionlog/joomla/src/Extension/Joomla.php index 360cab89df5aa..4c407485209e3 100644 --- a/plugins/actionlog/joomla/src/Extension/Joomla.php +++ b/plugins/actionlog/joomla/src/Extension/Joomla.php @@ -11,6 +11,7 @@ namespace Joomla\Plugin\Actionlog\Joomla\Extension; use Joomla\CMS\Component\ComponentHelper; +use Joomla\CMS\Event\AbstractEvent; use Joomla\CMS\Event\Application; use Joomla\CMS\Event\Cache; use Joomla\CMS\Event\Checkin; @@ -131,6 +132,7 @@ public static function getSubscribedEvents(): array 'onUserAfterResetRequest' => 'onUserAfterResetRequest', 'onUserAfterResetComplete' => 'onUserAfterResetComplete', 'onUserBeforeSave' => 'onUserBeforeSave', + 'onBeforeTourSaveUserState' => 'onBeforeTourSaveUserState', ]; } @@ -1301,4 +1303,56 @@ public function onUserBeforeSave(User\BeforeSaveEvent $event): void $session->set('block', $blockunblock); } } + + /** + * Method is called when a user cancels, completes or skips a tour + * + * @param AbstractEvent $event The event instance. + * + * @return void + * + * @since __DEPLOY_VERSION__ + */ + public function onBeforeTourSaveUserState(AbstractEvent $event): void + { + $option = $this->getApplication()->getInput()->get('option'); + + if (!$this->checkLoggable($option)) { + return; + } + + $tourId = $event->getArgument('tourId'); + $state = $event->getArgument('actionState'); + $stepNumber = $event->getArgument('stepNumber'); + + switch ($state) { + case 'skipped': + $messageLanguageKey = 'PLG_ACTIONLOG_JOOMLA_GUIDEDTOURS_TOURSKIPPED'; + break; + case 'completed': + $messageLanguageKey = 'PLG_ACTIONLOG_JOOMLA_GUIDEDTOURS_TOURCOMPLETED'; + break; + default: + $messageLanguageKey = 'PLG_ACTIONLOG_JOOMLA_GUIDEDTOURS_TOURDELAYED'; + } + + // Get the tour from the model to fetch the translated title of the tour + $factory = $this->getApplication()->bootComponent('com_guidedtours')->getMVCFactory(); + $tourModel = $factory->createModel( + 'Tour', + 'Administrator', + ['ignore_request' => true] + ); + + $tour = $tourModel->getItem($tourId); + + $message = [ + 'id' => $tourId, + 'title' => $tour->title_translation, + 'state' => $state, + 'step' => $stepNumber, + ]; + + $this->addLog([$message], $messageLanguageKey, 'com_guidedtours.state'); + } } diff --git a/plugins/system/guidedtours/services/provider.php b/plugins/system/guidedtours/services/provider.php index 8e30afd6b0b47..d372a3a0c2de9 100644 --- a/plugins/system/guidedtours/services/provider.php +++ b/plugins/system/guidedtours/services/provider.php @@ -14,6 +14,7 @@ use Joomla\CMS\Factory; use Joomla\CMS\Plugin\PluginHelper; use Joomla\CMS\WebAsset\WebAssetRegistry; +use Joomla\Database\DatabaseInterface; use Joomla\DI\Container; use Joomla\DI\ServiceProviderInterface; use Joomla\Event\DispatcherInterface; @@ -43,6 +44,7 @@ function (Container $container) { ); $plugin->setApplication($app); + $plugin->setDatabase($container->get(DatabaseInterface::class)); $wa = $container->get(WebAssetRegistry::class); diff --git a/plugins/system/guidedtours/src/Extension/GuidedTours.php b/plugins/system/guidedtours/src/Extension/GuidedTours.php index 4bcc765ad39fc..76844f7d61811 100644 --- a/plugins/system/guidedtours/src/Extension/GuidedTours.php +++ b/plugins/system/guidedtours/src/Extension/GuidedTours.php @@ -10,12 +10,17 @@ namespace Joomla\Plugin\System\GuidedTours\Extension; +use Joomla\CMS\Component\ComponentHelper; +use Joomla\CMS\Date\Date; +use Joomla\CMS\Language\Multilanguage; use Joomla\CMS\Language\Text; use Joomla\CMS\Object\CMSObject; use Joomla\CMS\Plugin\CMSPlugin; use Joomla\CMS\Session\Session; use Joomla\Component\Guidedtours\Administrator\Extension\GuidedtoursComponent; use Joomla\Component\Guidedtours\Administrator\Model\TourModel; +use Joomla\Database\DatabaseAwareTrait; +use Joomla\Database\ParameterType; use Joomla\Event\DispatcherInterface; use Joomla\Event\Event; use Joomla\Event\SubscriberInterface; @@ -31,6 +36,8 @@ */ final class GuidedTours extends CMSPlugin implements SubscriberInterface { + use DatabaseAwareTrait; + /** * A mapping for the step types * @@ -141,41 +148,106 @@ public function onBeforeCompileHead() $user = $app->getIdentity(); if ($user != null && $user->id > 0) { - // Load plugin language files + // Load plugin language files. $this->loadLanguage(); Text::script('JCANCEL'); Text::script('PLG_SYSTEM_GUIDEDTOURS_BACK'); Text::script('PLG_SYSTEM_GUIDEDTOURS_COMPLETE'); Text::script('PLG_SYSTEM_GUIDEDTOURS_COULD_NOT_LOAD_THE_TOUR'); + Text::script('PLG_SYSTEM_GUIDEDTOURS_HIDE_FOREVER'); Text::script('PLG_SYSTEM_GUIDEDTOURS_NEXT'); - Text::script('PLG_SYSTEM_GUIDEDTOURS_START'); Text::script('PLG_SYSTEM_GUIDEDTOURS_STEP_NUMBER_OF'); Text::script('PLG_SYSTEM_GUIDEDTOURS_TOUR_ERROR'); + Text::script('PLG_SYSTEM_GUIDEDTOURS_TOUR_ERROR_RESPONSE'); + Text::script('PLG_SYSTEM_GUIDEDTOURS_TOUR_INVALID_RESPONSE'); $doc->addScriptOptions('com_guidedtours.token', Session::getFormToken()); + $doc->addScriptOptions('com_guidedtours.autotour', ''); - // Load required assets + // Load required assets. $doc->getWebAssetManager() ->usePreset('plg_system_guidedtours.guidedtours'); - // Temporary solution to auto-start the welcome tour - if ($app->getInput()->getCmd('option', 'com_cpanel') === 'com_cpanel') { - $factory = $app->bootComponent('com_guidedtours')->getMVCFactory(); + $params = ComponentHelper::getParams('com_guidedtours'); - $tourModel = $factory->createModel( - 'Tour', - 'Administrator', - ['ignore_request' => true] - ); + // Check if the user has opted out of auto-start + $userAuthorizedAutostart = $user->getParam('allowTourAutoStart', $params->get('allowTourAutoStart', 1)); + if (!$userAuthorizedAutostart) { + return; + } - if ($tourModel->isAutostart('joomla-welcome')) { - $tour = $this->getTour('joomla-welcome'); + // The following code only relates to the auto-start functionality. + // First, we get the tours for the context. - $doc->addScriptOptions('com_guidedtours.autotour', $tour->id); + $factory = $app->bootComponent('com_guidedtours')->getMVCFactory(); + + $toursModel = $factory->createModel( + 'Tours', + 'Administrator', + ['ignore_request' => true] + ); + + $toursModel->setState('filter.extension', $app->getInput()->getCmd('option', 'com_cpanel')); + $toursModel->setState('filter.published', 1); + $toursModel->setState('filter.access', $user->getAuthorisedViewLevels()); - // Set autostart to '0' to avoid it to autostart again - $tourModel->setAutostart($tour->id, 0); + if (Multilanguage::isEnabled()) { + $toursModel->setState('filter.language', ['*', $app->getLanguage()->getTag()]); + } + + $tours = $toursModel->getItems(); + foreach ($tours as $tour) { + // Look for the first autostart tour, if any. + if ($tour->autostart) { + $db = $this->getDatabase(); + $profileKey = 'guidedtour.id.' . $tour->id; + + // Check if the tour state has already been saved some time before. + $query = $db->getQuery(true) + ->select($db->quoteName('profile_value')) + ->from($db->quoteName('#__user_profiles')) + ->where($db->quoteName('user_id') . ' = :user_id') + ->where($db->quoteName('profile_key') . ' = :profileKey') + ->bind(':user_id', $user->id, ParameterType::INTEGER) + ->bind(':profileKey', $profileKey, ParameterType::STRING); + + try { + $result = $db->setQuery($query)->loadResult(); + } catch (\Exception $e) { + // Do not start the tour. + continue; + } + + // A result has been found in the user profiles table + if (!\is_null($result)) { + $values = json_decode($result, true); + + if (empty($values)) { + // Do not start the tour. + continue; + } + + if ($values['state'] === 'skipped' || $values['state'] === 'completed') { + // Do not start the tour. + continue; + } + + if ($values['state'] === 'delayed') { + $delay = $params->get('delayed_time', '60'); + $currentTime = Date::getInstance(); + $loggedTime = new Date($values['time']['date']); + + if ($loggedTime->add(new \DateInterval('PT' . $delay . 'M')) > $currentTime) { + // Do not start the tour. + continue; + } + } + } + + // We have a tour to auto start. No need to go any further. + $doc->addScriptOptions('com_guidedtours.autotour', $tour->id); + break; } } } @@ -259,6 +331,9 @@ private function processTour($item) // Replace 'images/' to '../images/' when using an image from /images in backend. $temp->description = preg_replace('*src\=\"(?!administrator\/)images/*', 'src="../images/', $temp->description); + // Set the start label for the tour. + $temp->start_label = Text::_('PLG_SYSTEM_GUIDEDTOURS_START'); + $tour->steps[] = $temp; foreach ($steps as $i => $step) {
The text was updated successfully, but these errors were encountered:
fix joomlagerman#3261
e172464
translation
PR #3264
Sorry, something went wrong.
[5.2] [Guided tours] Auto start tours - full functionality (#3264)
22c0b10
* add new strings * fix #3261 translation * Update administrator/language/de-DE/plg_system_guidedtours.ini --------- Co-authored-by: Tobias Zulauf <[email protected]>
tecpromotion
Successfully merging a pull request may close this issue.
New language relevant PR in upstream repo: joomla/joomla-cms#43814 Here are the upstream changes:
Click to expand the diff!
The text was updated successfully, but these errors were encountered: