primo commit
This commit is contained in:
		
							
								
								
									
										386
									
								
								plugins/system/guidedtours/src/Extension/GuidedTours.php
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										386
									
								
								plugins/system/guidedtours/src/Extension/GuidedTours.php
									
									
									
									
									
										Normal file
									
								
							| @ -0,0 +1,386 @@ | ||||
| <?php | ||||
|  | ||||
| /** | ||||
|  * @package     Joomla.Plugin | ||||
|  * @subpackage  System.guidedtours | ||||
|  * | ||||
|  * @copyright   (C) 2023 Open Source Matters, Inc. <https://www.joomla.org> | ||||
|  * @license     GNU General Public License version 2 or later; see LICENSE.txt | ||||
|  */ | ||||
|  | ||||
| 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; | ||||
|  | ||||
| // phpcs:disable PSR1.Files.SideEffects | ||||
| \defined('_JEXEC') or die; | ||||
| // phpcs:enable PSR1.Files.SideEffects | ||||
|  | ||||
| /** | ||||
|  * Guided Tours plugin to add interactive tours to the administrator interface. | ||||
|  * | ||||
|  * @since  4.3.0 | ||||
|  */ | ||||
| final class GuidedTours extends CMSPlugin implements SubscriberInterface | ||||
| { | ||||
|     use DatabaseAwareTrait; | ||||
|  | ||||
|     /** | ||||
|      * A mapping for the step types | ||||
|      * | ||||
|      * @var    string[] | ||||
|      * @since  4.3.0 | ||||
|      */ | ||||
|     protected $stepType = [ | ||||
|         GuidedtoursComponent::STEP_NEXT        => 'next', | ||||
|         GuidedtoursComponent::STEP_REDIRECT    => 'redirect', | ||||
|         GuidedtoursComponent::STEP_INTERACTIVE => 'interactive', | ||||
|     ]; | ||||
|  | ||||
|     /** | ||||
|      * A mapping for the step interactive types | ||||
|      * | ||||
|      * @var    string[] | ||||
|      * @since  4.3.0 | ||||
|      */ | ||||
|     protected $stepInteractiveType = [ | ||||
|         GuidedtoursComponent::STEP_INTERACTIVETYPE_FORM_SUBMIT    => 'submit', | ||||
|         GuidedtoursComponent::STEP_INTERACTIVETYPE_TEXT           => 'text', | ||||
|         GuidedtoursComponent::STEP_INTERACTIVETYPE_OTHER          => 'other', | ||||
|         GuidedtoursComponent::STEP_INTERACTIVETYPE_BUTTON         => 'button', | ||||
|         GuidedtoursComponent::STEP_INTERACTIVETYPE_CHECKBOX_RADIO => 'checkbox_radio', | ||||
|         GuidedtoursComponent::STEP_INTERACTIVETYPE_SELECT         => 'select', | ||||
|     ]; | ||||
|  | ||||
|     /** | ||||
|      * An internal flag whether plugin should listen any event. | ||||
|      * | ||||
|      * @var bool | ||||
|      * | ||||
|      * @since   4.3.0 | ||||
|      */ | ||||
|     protected static $enabled = false; | ||||
|  | ||||
|     /** | ||||
|      * Constructor | ||||
|      * | ||||
|      * @param   DispatcherInterface  $dispatcher  The object to observe | ||||
|      * @param   array                $config      An optional associative array of configuration settings. | ||||
|      * @param   boolean              $enabled     An internal flag whether plugin should listen any event. | ||||
|      * | ||||
|      * @since   4.3.0 | ||||
|      */ | ||||
|     public function __construct(DispatcherInterface $dispatcher, array $config = [], bool $enabled = false) | ||||
|     { | ||||
|         self::$enabled = $enabled; | ||||
|  | ||||
|         parent::__construct($dispatcher, $config); | ||||
|     } | ||||
|  | ||||
|     /** | ||||
|      * function for getSubscribedEvents : new Joomla 4 feature | ||||
|      * | ||||
|      * @return array | ||||
|      * | ||||
|      * @since   4.3.0 | ||||
|      */ | ||||
|     public static function getSubscribedEvents(): array | ||||
|     { | ||||
|         return self::$enabled ? [ | ||||
|             'onAjaxGuidedtours'   => 'startTour', | ||||
|             'onBeforeCompileHead' => 'onBeforeCompileHead', | ||||
|         ] : []; | ||||
|     } | ||||
|  | ||||
|     /** | ||||
|      * Retrieve and starts a tour and its steps through Ajax. | ||||
|      * | ||||
|      * @return null|object | ||||
|      * | ||||
|      * @since   4.3.0 | ||||
|      */ | ||||
|     public function startTour(Event $event) | ||||
|     { | ||||
|         $tourId  = (int) $this->getApplication()->getInput()->getInt('id'); | ||||
|         $tourUid = $this->getApplication()->getInput()->getString('uid', ''); | ||||
|         $tourUid = $tourUid !== '' ? urldecode($tourUid) : ''; | ||||
|  | ||||
|         $tour = null; | ||||
|  | ||||
|         // Load plugin language files | ||||
|         $this->loadLanguage(); | ||||
|  | ||||
|         if ($tourId > 0) { | ||||
|             $tour = $this->getTour($tourId); | ||||
|         } elseif ($tourUid !== '') { | ||||
|             $tour = $this->getTour($tourUid); | ||||
|         } | ||||
|  | ||||
|         $event->setArgument('result', $tour ?? new \stdClass()); | ||||
|  | ||||
|         return $tour; | ||||
|     } | ||||
|  | ||||
|     /** | ||||
|      * Listener for the `onBeforeCompileHead` event | ||||
|      * | ||||
|      * @return  void | ||||
|      * | ||||
|      * @since   4.3.0 | ||||
|      */ | ||||
|     public function onBeforeCompileHead() | ||||
|     { | ||||
|         $app  = $this->getApplication(); | ||||
|         $doc  = $app->getDocument(); | ||||
|         $user = $app->getIdentity(); | ||||
|  | ||||
|         if ($user != null && $user->id > 0) { | ||||
|             // 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_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. | ||||
|             $doc->getWebAssetManager() | ||||
|                 ->usePreset('plg_system_guidedtours.guidedtours'); | ||||
|  | ||||
|             $params = ComponentHelper::getParams('com_guidedtours'); | ||||
|  | ||||
|             // Check if the user has opted out of auto-start | ||||
|             $userAuthorizedAutostart = $user->getParam('allowTourAutoStart', $params->get('allowTourAutoStart', 1)); | ||||
|             if (!$userAuthorizedAutostart) { | ||||
|                 return; | ||||
|             } | ||||
|  | ||||
|             // The following code only relates to the auto-start functionality. | ||||
|             // First, we get the tours for the context. | ||||
|  | ||||
|             $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()); | ||||
|  | ||||
|             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; | ||||
|                 } | ||||
|             } | ||||
|         } | ||||
|     } | ||||
|  | ||||
|     /** | ||||
|      * Get a tour and its steps or null if not found | ||||
|      * | ||||
|      * @param   integer|string  $tourId  The ID or Uid of the tour to load | ||||
|      * | ||||
|      * @return null|object | ||||
|      * | ||||
|      * @since   4.3.0 | ||||
|      */ | ||||
|     private function getTour($tourId) | ||||
|     { | ||||
|         $app = $this->getApplication(); | ||||
|  | ||||
|         $factory = $app->bootComponent('com_guidedtours')->getMVCFactory(); | ||||
|  | ||||
|         /** @var TourModel $tourModel */ | ||||
|         $tourModel = $factory->createModel( | ||||
|             'Tour', | ||||
|             'Administrator', | ||||
|             ['ignore_request' => true] | ||||
|         ); | ||||
|  | ||||
|         $item = $tourModel->getItem($tourId); | ||||
|  | ||||
|         return $this->processTour($item); | ||||
|     } | ||||
|  | ||||
|     /** | ||||
|      * Return a tour and its steps or null if not found | ||||
|      * | ||||
|      * @param   CMSObject  $item  The tour to load | ||||
|      * | ||||
|      * @return null|object | ||||
|      * | ||||
|      * @since   5.0.0 | ||||
|      */ | ||||
|     private function processTour($item) | ||||
|     { | ||||
|         $app = $this->getApplication(); | ||||
|  | ||||
|         $user    = $app->getIdentity(); | ||||
|         $factory = $app->bootComponent('com_guidedtours')->getMVCFactory(); | ||||
|  | ||||
|         if (empty($item->id) || $item->published < 1 || !\in_array($item->access, $user->getAuthorisedViewLevels())) { | ||||
|             return null; | ||||
|         } | ||||
|  | ||||
|         // We don't want to show all parameters, so take only a subset of the tour attributes | ||||
|         $tour = new \stdClass(); | ||||
|  | ||||
|         $tour->id        = $item->id; | ||||
|         $tour->autostart = $item->autostart; | ||||
|  | ||||
|         $stepsModel = $factory->createModel( | ||||
|             'Steps', | ||||
|             'Administrator', | ||||
|             ['ignore_request' => true] | ||||
|         ); | ||||
|  | ||||
|         $stepsModel->setState('filter.tour_id', $item->id); | ||||
|         $stepsModel->setState('filter.published', 1); | ||||
|         $stepsModel->setState('list.ordering', 'a.ordering'); | ||||
|         $stepsModel->setState('list.direction', 'ASC'); | ||||
|  | ||||
|         $steps = $stepsModel->getItems(); | ||||
|  | ||||
|         $tour->steps = []; | ||||
|  | ||||
|         $temp = new \stdClass(); | ||||
|  | ||||
|         $temp->id          = 0; | ||||
|         $temp->title       = $this->getApplication()->getLanguage()->_($item->title); | ||||
|         $temp->description = $this->getApplication()->getLanguage()->_($item->description); | ||||
|         $temp->description = $this->fixImagePaths($temp->description); | ||||
|         $temp->url         = $item->url; | ||||
|  | ||||
|         // Set the start label for the tour. | ||||
|         $temp->start_label = Text::_('PLG_SYSTEM_GUIDEDTOURS_START'); | ||||
|         // What's new tours have a different label. | ||||
|         if (str_contains($item->uid, 'joomla-whatsnew')) { | ||||
|             $temp->start_label = Text::_('PLG_SYSTEM_GUIDEDTOURS_NEXT'); | ||||
|         } | ||||
|  | ||||
|         $tour->steps[] = $temp; | ||||
|  | ||||
|         foreach ($steps as $i => $step) { | ||||
|             $temp = new \stdClass(); | ||||
|  | ||||
|             $temp->id               = $i + 1; | ||||
|             $temp->title            = $this->getApplication()->getLanguage()->_($step->title); | ||||
|             $temp->description      = $this->getApplication()->getLanguage()->_($step->description); | ||||
|             $temp->description      = $this->fixImagePaths($temp->description); | ||||
|             $temp->position         = $step->position; | ||||
|             $temp->target           = $step->target; | ||||
|             $temp->type             = $this->stepType[$step->type]; | ||||
|             $temp->interactive_type = $this->stepInteractiveType[$step->interactive_type]; | ||||
|             $temp->params           = $step->params; | ||||
|             $temp->url              = $step->url; | ||||
|             $temp->tour_id          = $step->tour_id; | ||||
|             $temp->step_id          = $step->id; | ||||
|  | ||||
|             $tour->steps[] = $temp; | ||||
|         } | ||||
|  | ||||
|         return $tour; | ||||
|     } | ||||
|  | ||||
|     /** | ||||
|      * Return a modified version of a given string with usable image paths for tours | ||||
|      * | ||||
|      * @param   string  $description  The string to fix | ||||
|      * | ||||
|      * @return  string | ||||
|      * | ||||
|      * @since  5.2.0 | ||||
|      */ | ||||
|     private function fixImagePaths($description) | ||||
|     { | ||||
|         return preg_replace( | ||||
|             [ | ||||
|                 '*src="(?!administrator\/)images/*', | ||||
|                 '*src="media/*', | ||||
|             ], | ||||
|             [ | ||||
|                 'src="../images/', | ||||
|                 'src="../media/', | ||||
|             ], | ||||
|             $description | ||||
|         ); | ||||
|     } | ||||
| } | ||||
		Reference in New Issue
	
	Block a user