Skip to content

Commit 9275907

Browse files
[5.2] [Guided tours] Auto start tours - full functionality (joomla#43814)
Co-authored-by: Brian Teeman <[email protected]>
1 parent 8de07cc commit 9275907

File tree

21 files changed

+544
-50
lines changed

21 files changed

+544
-50
lines changed

administrator/components/com_actionlogs/config.xml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -30,7 +30,7 @@
3030
label="COM_ACTIONLOGS_LOG_EXTENSIONS_LABEL"
3131
multiple="true"
3232
layout="joomla.form.field.list-fancy-select"
33-
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"
33+
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"
3434
/>
3535
<field
3636
name="loggable_api"
Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,7 @@
1+
--
2+
-- Add the Guided Tours selectable option to the User Action Logs
3+
--
4+
INSERT INTO `#__action_logs_extensions` (`extension`) VALUES ('com_guidedtours');
5+
6+
INSERT INTO `#__action_log_config` (`type_title`, `type_alias`, `id_holder`, `title_holder`, `table_name`, `text_prefix`) VALUES
7+
('guidedtour', 'com_guidedtours.state', 'id', 'title', '#__guidedtours', 'PLG_ACTIONLOG_JOOMLA');
Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,7 @@
1+
--
2+
-- Add the Guided Tours selectable option to the User Action Logs
3+
--
4+
INSERT INTO "#__action_logs_extensions" ("extension") VALUES ("com_guidedtours");
5+
6+
INSERT INTO "#__action_log_config" ("type_title", "type_alias", "id_holder", "title_holder", "table_name", "text_prefix") VALUES
7+
('guidedtour', 'com_guidedtours.state', 'id', 'title', '#__guidedtours', 'PLG_ACTIONLOG_JOOMLA');

administrator/components/com_guidedtours/config.xml

Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,28 @@
11
<?xml version="1.0" encoding="UTF-8"?>
22
<config>
3+
<fieldset name="guidedtours_config" label="COM_GUIDEDTOURS">
4+
<field
5+
name="allowTourAutoStart"
6+
type="radio"
7+
label="COM_GUIDEDTOURS_CONFIG_USER_ALLOWTOURAUTOSTART_LABEL"
8+
description="COM_GUIDEDTOURS_CONFIG_USER_ALLOWTOURAUTOSTART_DESCRIPTION"
9+
layout="joomla.form.field.radio.switcher"
10+
default="1"
11+
>
12+
<option value="0">JNO</option>
13+
<option value="1">JYES</option>
14+
</field>
15+
16+
<field
17+
name="delayed_time"
18+
type="text"
19+
label="COM_GUIDEDTOURS_CONFIG_DELAYED_TIME_LABEL"
20+
description="COM_GUIDEDTOURS_CONFIG_DELAYED_TIME_DESCRIPTION"
21+
default="60"
22+
size="small"
23+
showon="allowTourAutoStart:1"
24+
/>
25+
</fieldset>
326
<fieldset
427
name="permissions"
528
label="JCONFIG_PERMISSIONS_LABEL"
Lines changed: 124 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,124 @@
1+
<?php
2+
3+
/**
4+
* @package Joomla.Administrator
5+
* @subpackage com_guidedtours
6+
*
7+
* @copyright (C) 2024 Open Source Matters, Inc. <https://www.joomla.org>
8+
* @license GNU General Public License version 2 or later; see LICENSE.txt
9+
*/
10+
11+
namespace Joomla\Component\Guidedtours\Administrator\Controller;
12+
13+
use Joomla\CMS\Event\AbstractEvent;
14+
use Joomla\CMS\Language\Text;
15+
use Joomla\CMS\MVC\Controller\BaseController;
16+
use Joomla\CMS\Plugin\PluginHelper;
17+
use Joomla\CMS\Response\JsonResponse;
18+
19+
// phpcs:disable PSR1.Files.SideEffects
20+
\defined('_JEXEC') or die;
21+
// phpcs:enable PSR1.Files.SideEffects
22+
23+
/**
24+
* The guided tours controller for ajax requests.
25+
*
26+
* @since __DEPLOY_VERSION__
27+
*/
28+
class AjaxController extends BaseController
29+
{
30+
/**
31+
* Ajax call used when cancelling, skipping or completing a tour.
32+
* It allows:
33+
* - the trigering of before and after events the user state is recorded
34+
* - the recording of the user behavior in the action logs
35+
*/
36+
public function fetchUserState()
37+
{
38+
$user = $this->app->getIdentity();
39+
40+
$tourId = $this->app->input->getInt('tid', 0);
41+
$stepNumber = $this->app->input->getString('sid', '');
42+
$context = $this->app->input->getString('context', '');
43+
44+
if ($user != null && $user->id > 0) {
45+
$actionState = '';
46+
47+
switch ($context) {
48+
case 'tour.complete':
49+
$actionState = 'completed';
50+
break;
51+
case 'tour.cancel':
52+
$actionState = 'delayed';
53+
break;
54+
case 'tour.skip':
55+
$actionState = 'skipped';
56+
break;
57+
}
58+
59+
PluginHelper::importPlugin('guidedtours');
60+
61+
// event onBeforeTourSaveUserState before save user tour state
62+
$beforeEvent = AbstractEvent::create(
63+
'onBeforeTourSaveUserState',
64+
[
65+
'subject' => new \stdClass(),
66+
'tourId' => $tourId,
67+
'actionState' => $actionState,
68+
'stepNumber' => $stepNumber,
69+
]
70+
);
71+
72+
$this->app->getDispatcher()->dispatch('onBeforeTourSaveUserState', $beforeEvent);
73+
74+
// Save the tour state only when the tour auto-starts.
75+
$tourModel = $this->getModel('Tour', 'Administrator');
76+
if ($tourModel->isAutostart($tourId)) {
77+
$result = $tourModel->saveTourUserState($tourId, $actionState);
78+
if ($result) {
79+
$message = Text::sprintf('COM_GUIDEDTOURS_USERSTATE_STATESAVED', $user->id, $tourId);
80+
} else {
81+
$message = Text::sprintf('COM_GUIDEDTOURS_USERSTATE_STATENOTSAVED', $user->id, $tourId);
82+
}
83+
} else {
84+
$result = false;
85+
$message = Text::sprintf('COM_GUIDEDTOURS_USERSTATE_STATENOTSAVED', $user->id, $tourId);
86+
}
87+
88+
// event onAfterTourSaveUserState after save user tour state (may override message)
89+
$afterEvent = AbstractEvent::create(
90+
'onAfterTourSaveUserState',
91+
[
92+
'subject' => new \stdClass(),
93+
'tourId' => $tourId,
94+
'actionState' => $actionState,
95+
'stepNumber' => $stepNumber,
96+
'result' => $result,
97+
'message' => &$message,
98+
]
99+
);
100+
101+
$this->app->getDispatcher()->dispatch('onAfterTourSaveUserState', $afterEvent);
102+
103+
// Construct the response data
104+
$data = [
105+
'tourId' => $tourId,
106+
'stepId' => $stepNumber,
107+
'context' => $context,
108+
'state' => $actionState,
109+
];
110+
echo new JsonResponse($data, $message);
111+
$this->app->close();
112+
} else {
113+
// Construct the response data
114+
$data = [
115+
'success' => false,
116+
'tourId' => $tourId,
117+
];
118+
119+
$message = Text::_('COM_GUIDEDTOURS_USERSTATE_CONNECTEDONLY');
120+
echo new JsonResponse($data, $message, true);
121+
$this->app->close();
122+
}
123+
}
124+
}

administrator/components/com_guidedtours/src/Model/TourModel.php

Lines changed: 74 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,7 @@
1010

1111
namespace Joomla\Component\Guidedtours\Administrator\Model;
1212

13+
use Joomla\CMS\Date\Date;
1314
use Joomla\CMS\Factory;
1415
use Joomla\CMS\Language\Text;
1516
use Joomla\CMS\Log\Log;
@@ -530,20 +531,28 @@ public function setAutostart($id, $autostart)
530531
/**
531532
* Retrieve a tour's autostart value
532533
*
533-
* @param string $uid the uid of a tour
534+
* @param string $pk the id or uid of a tour
535+
*
536+
* @return boolean
534537
*
535538
* @since 5.1.0
536539
*/
537-
public function isAutostart($uid)
540+
public function isAutostart($pk): bool
538541
{
539542
$db = $this->getDatabase();
540543

541544
$query = $db->getQuery(true)
542545
->select($db->quoteName('autostart'))
543546
->from($db->quoteName('#__guidedtours'))
544-
->where($db->quoteName('published') . ' = 1')
545-
->where($db->quoteName('uid') . ' = :uid')
546-
->bind(':uid', $uid, ParameterType::STRING);
547+
->where($db->quoteName('published') . ' = 1');
548+
549+
if (\is_integer($pk)) {
550+
$query->where($db->quoteName('id') . ' = :id')
551+
->bind(':id', $pk, ParameterType::INTEGER);
552+
} else {
553+
$query->where($db->quoteName('uid') . ' = :uid')
554+
->bind(':uid', $pk, ParameterType::STRING);
555+
}
547556

548557
$db->setQuery($query);
549558

@@ -558,4 +567,64 @@ public function isAutostart($uid)
558567

559568
return $result;
560569
}
570+
571+
/**
572+
* Save a tour state for a specific user.
573+
*
574+
* @param int $id The id of the tour
575+
* @param string $state The label of the state to be saved (completed, delayed or skipped)
576+
*
577+
* @return boolean
578+
*
579+
* @since __DEPLOY_VERSION__
580+
*/
581+
public function saveTourUserState($id, $state = ''): bool
582+
{
583+
$user = $this->getCurrentUser();
584+
$db = $this->getDatabase();
585+
586+
$profileKey = 'guidedtour.id.' . $id;
587+
588+
// Check if the profile key already exists.
589+
$query = $db->getQuery(true)
590+
->select($db->quoteName('profile_value'))
591+
->from($db->quoteName('#__user_profiles'))
592+
->where($db->quoteName('user_id') . ' = :user_id')
593+
->where($db->quoteName('profile_key') . ' = :profileKey')
594+
->bind(':user_id', $user->id, ParameterType::INTEGER)
595+
->bind(':profileKey', $profileKey, ParameterType::STRING);
596+
597+
try {
598+
$result = $db->setQuery($query)->loadResult();
599+
} catch (\Exception $e) {
600+
return false;
601+
}
602+
603+
$tourState = [];
604+
605+
$tourState['state'] = $state;
606+
if ($state === 'delayed') {
607+
$tourState['time'] = Date::getInstance();
608+
}
609+
610+
$profileObject = (object)[
611+
'user_id' => $user->id,
612+
'profile_key' => $profileKey,
613+
'profile_value' => json_encode($tourState),
614+
'ordering' => 0,
615+
];
616+
617+
if (!\is_null($result)) {
618+
$values = json_decode($result, true);
619+
620+
// The profile is updated only when delayed. 'Completed' and 'Skipped' are final
621+
if (!empty($values) && $values['state'] === 'delayed') {
622+
$db->updateObject('#__user_profiles', $profileObject, ['user_id', 'profile_key']);
623+
}
624+
} else {
625+
$db->insertObject('#__user_profiles', $profileObject);
626+
}
627+
628+
return true;
629+
}
561630
}

administrator/components/com_users/forms/user.xml

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -164,6 +164,18 @@
164164
<option value="dark">COM_USERS_USER_COLORSCHEME_OPTION_DARK</option>
165165
</field>
166166

167+
<field
168+
name="allowTourAutoStart"
169+
type="list"
170+
label="COM_USERS_USER_ALLOWTOURAUTOSTART_LABEL"
171+
default=""
172+
validate="options"
173+
>
174+
<option value="">JOPTION_USE_DEFAULT</option>
175+
<option value="0">JNO</option>
176+
<option value="1">JYES</option>
177+
</field>
178+
167179
<field
168180
name="admin_language"
169181
type="language"

administrator/language/en-GB/com_guidedtours.ini

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,10 @@
66
COM_GUIDEDTOURS="Guided Tours"
77
COM_GUIDEDTOURS_AUTOSTART_DESC="Start the tour automatically when a user reaches the context in which the tour should be displayed."
88
COM_GUIDEDTOURS_AUTOSTART_LABEL="Auto Start"
9+
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."
10+
COM_GUIDEDTOURS_CONFIG_DELAYED_TIME_LABEL="Auto Start Time Delay (in minutes)"
11+
COM_GUIDEDTOURS_CONFIG_USER_ALLOWTOURAUTOSTART_DESCRIPTION="Turn on or off the auto starting functionality of tours."
12+
COM_GUIDEDTOURS_CONFIG_USER_ALLOWTOURAUTOSTART_LABEL="Allow Auto Starting Tours"
913
COM_GUIDEDTOURS_CONFIGURATION="Guided Tours: Options"
1014
COM_GUIDEDTOURS_DESCRIPTION="Description"
1115
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
9094
COM_GUIDEDTOURS_TYPE_REDIRECT_URL_LABEL="Relative URL"
9195
COM_GUIDEDTOURS_URL_LABEL="Relative URL"
9296
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."
97+
COM_GUIDEDTOURS_USERSTATE_CONNECTEDONLY="Tour User state action is only for connected users."
98+
COM_GUIDEDTOURS_USERSTATE_STATENOTSAVED="Tour User state not saved for user %1$s tour %2$s."
99+
COM_GUIDEDTOURS_USERSTATE_STATESAVED="Tour User state saved for user %1$s tour %2$s."
93100
COM_GUIDEDTOURS_XML_DESCRIPTION="Component for managing Guided Tours functionality."

administrator/language/en-GB/com_users.ini

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -394,6 +394,7 @@ COM_USERS_USERS_N_ITEMS_DELETED="%d users deleted."
394394
COM_USERS_USERS_N_ITEMS_DELETED_1="User deleted."
395395
COM_USERS_USERS_TABLE_CAPTION="Users"
396396
COM_USERS_USER_ACCOUNT_DETAILS="Account Details"
397+
COM_USERS_USER_ALLOWTOURAUTOSTART_LABEL="Allow Auto Starting Tours"
397398
COM_USERS_USER_BACKUPCODE="Backup Code"
398399
COM_USERS_USER_BACKUPCODES="Backup Codes"
399400
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."

administrator/language/en-GB/plg_actionlog_joomla.ini

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -63,3 +63,7 @@ PLG_ACTIONLOG_JOOMLA_EXTENSION_INSTALLED="User <a href='{accountlink}'>{username
6363
PLG_ACTIONLOG_JOOMLA_EXTENSION_UNINSTALLED="User <a href='{accountlink}'>{username}</a> uninstalled the {type} {extension_name}"
6464
PLG_ACTIONLOG_JOOMLA_EXTENSION_UPDATED="User <a href='{accountlink}'>{username}</a> updated the {type} {extension_name}"
6565
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>"
66+
; Guided Tours
67+
PLG_ACTIONLOG_JOOMLA_GUIDEDTOURS_TOURCOMPLETED="User <a href='{accountlink}'>{username}</a> completed the tour '{title}'"
68+
PLG_ACTIONLOG_JOOMLA_GUIDEDTOURS_TOURDELAYED="User <a href='{accountlink}'>{username}</a> delayed the tour '{title}'"
69+
PLG_ACTIONLOG_JOOMLA_GUIDEDTOURS_TOURSKIPPED="User <a href='{accountlink}'>{username}</a> skipped the tour '{title}'"

0 commit comments

Comments
 (0)